Merge branch 'master' into osgi
Conflicts: akka-core/pom.xml pom.xml Conflicts: akka-core/pom.xml pom.xml Conflicts: akka-core/pom.xml pom.xml
This commit is contained in:
commit
6a52fe4f44
159 changed files with 3961 additions and 2842 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -1,5 +1,9 @@
|
|||
*~
|
||||
*#
|
||||
project/boot/*
|
||||
*/project/build/target
|
||||
*/project/boot
|
||||
lib_managed
|
||||
etags
|
||||
TAGS
|
||||
reports
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>akka-amqp</artifactId>
|
||||
<name>Akka AMQP Module</name>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<artifactId>akka</artifactId>
|
||||
<groupId>se.scalablesolutions.akka</groupId>
|
||||
<version>0.7-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<artifactId>akka-core</artifactId>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.rabbitmq</groupId>
|
||||
<artifactId>amqp-client</artifactId>
|
||||
<version>1.7.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
|
@ -0,0 +1 @@
|
|||
class=se.scalablesolutions.akka.camel.component.ActorComponent
|
||||
95
akka-camel/src/main/scala/CamelContextLifecycle.scala
Normal file
95
akka-camel/src/main/scala/CamelContextLifecycle.scala
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
||||
*/
|
||||
|
||||
package se.scalablesolutions.akka.camel
|
||||
|
||||
import org.apache.camel.{ProducerTemplate, CamelContext}
|
||||
import org.apache.camel.impl.DefaultCamelContext
|
||||
|
||||
import se.scalablesolutions.akka.util.Logging
|
||||
|
||||
/**
|
||||
* Defines the lifecycle of a CamelContext. Allowed state transitions are
|
||||
* init -> start -> stop -> init -> ... etc.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
trait CamelContextLifecycle extends Logging {
|
||||
// TODO: enforce correct state transitions
|
||||
// valid: init -> start -> stop -> init ...
|
||||
|
||||
private var _context: CamelContext = _
|
||||
private var _template: ProducerTemplate = _
|
||||
|
||||
private var _initialized = false
|
||||
private var _started = false
|
||||
|
||||
/**
|
||||
* Returns the managed CamelContext.
|
||||
*/
|
||||
protected def context: CamelContext = _context
|
||||
|
||||
/**
|
||||
* Returns the managed ProducerTemplate.
|
||||
*/
|
||||
protected def template: ProducerTemplate = _template
|
||||
|
||||
/**
|
||||
* Sets the managed CamelContext.
|
||||
*/
|
||||
protected def context_= (context: CamelContext) { _context = context }
|
||||
|
||||
/**
|
||||
* Sets the managed ProducerTemplate.
|
||||
*/
|
||||
protected def template_= (template: ProducerTemplate) { _template = template }
|
||||
|
||||
def initialized = _initialized
|
||||
def started = _started
|
||||
|
||||
/**
|
||||
* Starts the CamelContext and ProducerTemplate.
|
||||
*/
|
||||
def start = {
|
||||
context.start
|
||||
template.start
|
||||
_started = true
|
||||
log.info("Camel context started")
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the CamelContext and ProducerTemplate.
|
||||
*/
|
||||
def stop = {
|
||||
template.stop
|
||||
context.stop
|
||||
_initialized = false
|
||||
_started = false
|
||||
log.info("Camel context stopped")
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this lifecycle object with the a DefaultCamelContext.
|
||||
*/
|
||||
def init: Unit = init(new DefaultCamelContext)
|
||||
|
||||
/**
|
||||
* Initializes this lifecycle object with the given CamelContext.
|
||||
*/
|
||||
def init(context: CamelContext) {
|
||||
this.context = context
|
||||
this.template = context.createProducerTemplate
|
||||
_initialized = true
|
||||
log.info("Camel context initialized")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a global CamelContext and ProducerTemplate accessible to applications. The lifecycle
|
||||
* of these objects is managed by se.scalablesolutions.akka.camel.service.CamelService.
|
||||
*/
|
||||
object CamelContextManager extends CamelContextLifecycle {
|
||||
override def context: CamelContext = super.context
|
||||
override def template: ProducerTemplate = super.template
|
||||
}
|
||||
20
akka-camel/src/main/scala/Consumer.scala
Normal file
20
akka-camel/src/main/scala/Consumer.scala
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
||||
*/
|
||||
|
||||
package se.scalablesolutions.akka.camel
|
||||
|
||||
import se.scalablesolutions.akka.actor.Actor
|
||||
|
||||
/**
|
||||
* Mixed in by Actor implementations that consume message from Camel endpoints.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
trait Consumer { self: Actor =>
|
||||
|
||||
/**
|
||||
* Returns the Camel endpoint URI to consume messages from.
|
||||
*/
|
||||
def endpointUri: String
|
||||
}
|
||||
249
akka-camel/src/main/scala/Message.scala
Normal file
249
akka-camel/src/main/scala/Message.scala
Normal file
|
|
@ -0,0 +1,249 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
||||
*/
|
||||
|
||||
package se.scalablesolutions.akka.camel
|
||||
|
||||
import org.apache.camel.{Exchange, Message => CamelMessage}
|
||||
import org.apache.camel.util.ExchangeHelper
|
||||
|
||||
import scala.collection.jcl.{Map => MapWrapper}
|
||||
|
||||
/**
|
||||
* An immutable representation of a Camel message. Actor classes that mix in
|
||||
* se.scalablesolutions.akka.camel.Producer or
|
||||
* se.scalablesolutions.akka.camel.Consumer use this message type for communication.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
case class Message(val body: Any, val headers: Map[String, Any]) {
|
||||
/**
|
||||
* Creates a message with a body and an empty header map.
|
||||
*/
|
||||
def this(body: Any) = this(body, Map.empty)
|
||||
|
||||
/**
|
||||
* Returns the body of the message converted to the type given by the <code>clazz</code>
|
||||
* argument. Conversion is done using Camel's type converter. The type converter is obtained
|
||||
* from the CamelContext managed by CamelContextManager. Applications have to ensure proper
|
||||
* initialization of CamelContextManager.
|
||||
*
|
||||
* @see CamelContextManager.
|
||||
*/
|
||||
def bodyAs[T](clazz: Class[T]): T =
|
||||
CamelContextManager.context.getTypeConverter.mandatoryConvertTo[T](clazz, body)
|
||||
|
||||
/**
|
||||
* Returns those headers from this message whose name is contained in <code>names</code>.
|
||||
*/
|
||||
def headers(names: Set[String]): Map[String, Any] = headers.filter(names contains _._1)
|
||||
|
||||
/**
|
||||
* Creates a Message with a new <code>body</code> using a <code>transformer</code> function.
|
||||
*/
|
||||
def transformBody[A](transformer: A => Any): Message = setBody(transformer(body.asInstanceOf[A]))
|
||||
|
||||
/**
|
||||
* Creates a Message with a new <code>body</code> converted to type <code>clazz</code>.
|
||||
*
|
||||
* @see Message#bodyAs(Class)
|
||||
*/
|
||||
def setBodyAs[T](clazz: Class[T]): Message = setBody(bodyAs(clazz))
|
||||
|
||||
/**
|
||||
* Creates a Message with a new <code>body</code>.
|
||||
*/
|
||||
def setBody(body: Any) = new Message(body, this.headers)
|
||||
|
||||
/**
|
||||
* Creates a new Message with new <code>headers</code>.
|
||||
*/
|
||||
def setHeaders(headers: Map[String, Any]) = new Message(this.body, headers)
|
||||
|
||||
/**
|
||||
* Creates a new Message with the <code>headers</code> argument added to the existing headers.
|
||||
*/
|
||||
def addHeaders(headers: Map[String, Any]) = new Message(this.body, this.headers ++ headers)
|
||||
|
||||
/**
|
||||
* Creates a new Message with the <code>header</code> argument added to the existing headers.
|
||||
*/
|
||||
def addHeader(header: (String, Any)) = new Message(this.body, this.headers + header)
|
||||
|
||||
/**
|
||||
* Creates a new Message where the header with name <code>headerName</code> is removed from
|
||||
* the existing headers.
|
||||
*/
|
||||
def removeHeader(headerName: String) = new Message(this.body, this.headers - headerName)
|
||||
}
|
||||
|
||||
/**
|
||||
* Companion object of Message class.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
object Message {
|
||||
|
||||
/**
|
||||
* Message header to correlate request with response messages. Applications that send
|
||||
* messages to a Producer actor may want to set this header on the request message
|
||||
* so that it can be correlated with an asynchronous response. Messages send to Consumer
|
||||
* actors have this header already set.
|
||||
*/
|
||||
val MessageExchangeId = "MessageExchangeId".intern
|
||||
|
||||
/**
|
||||
* Creates a new Message with <code>body</code> as message body and an empty header map.
|
||||
*/
|
||||
def apply(body: Any) = new Message(body)
|
||||
|
||||
/**
|
||||
* Creates a canonical form of the given message <code>msg</code>. If <code>msg</code> of type
|
||||
* Message then <code>msg</code> is returned, otherwise <code>msg</code> is set as body of a
|
||||
* newly created Message object.
|
||||
*/
|
||||
def canonicalize(msg: Any) = msg match {
|
||||
case mobj: Message => mobj
|
||||
case body => new Message(body)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An immutable representation of a failed Camel exchange. It contains the failure cause
|
||||
* obtained from Exchange.getException and the headers from either the Exchange.getIn
|
||||
* message or Exchange.getOut message, depending on the exchange pattern.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
case class Failure(val cause: Exception, val headers: Map[String, Any])
|
||||
|
||||
/**
|
||||
* Adapter for converting an org.apache.camel.Exchange to and from Message and Failure objects.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
class CamelExchangeAdapter(exchange: Exchange) {
|
||||
|
||||
import CamelMessageConversion.toMessageAdapter
|
||||
|
||||
/**
|
||||
* Sets Exchange.getIn from the given Message object.
|
||||
*/
|
||||
def fromRequestMessage(msg: Message): Exchange = { requestMessage.fromMessage(msg); exchange }
|
||||
|
||||
/**
|
||||
* Depending on the exchange pattern, sets Exchange.getIn or Exchange.getOut from the given
|
||||
* Message object. If the exchange is out-capable then the Exchange.getOut is set, otherwise
|
||||
* Exchange.getIn.
|
||||
*/
|
||||
def fromResponseMessage(msg: Message): Exchange = { responseMessage.fromMessage(msg); exchange }
|
||||
|
||||
/**
|
||||
* Sets Exchange.getException from the given Failure message. Headers of the Failure message
|
||||
* are ignored.
|
||||
*/
|
||||
def fromFailureMessage(msg: Failure): Exchange = { exchange.setException(msg.cause); exchange }
|
||||
|
||||
/**
|
||||
* Creates a Message object from Exchange.getIn.
|
||||
*/
|
||||
def toRequestMessage: Message = toRequestMessage(Map.empty)
|
||||
|
||||
/**
|
||||
* Depending on the exchange pattern, creates a Message object from Exchange.getIn or Exchange.getOut.
|
||||
* If the exchange is out-capable then the Exchange.getOut is set, otherwise Exchange.getIn.
|
||||
*/
|
||||
def toResponseMessage: Message = toResponseMessage(Map.empty)
|
||||
|
||||
/**
|
||||
* Creates a Failure object from the adapted Exchange.
|
||||
*
|
||||
* @see Failure
|
||||
*/
|
||||
def toFailureMessage: Failure = toFailureMessage(Map.empty)
|
||||
|
||||
/**
|
||||
* Creates a Message object from Exchange.getIn.
|
||||
*
|
||||
* @param headers additional headers to set on the created Message in addition to those
|
||||
* in the Camel message.
|
||||
*/
|
||||
def toRequestMessage(headers: Map[String, Any]): Message = requestMessage.toMessage(headers)
|
||||
|
||||
/**
|
||||
* Depending on the exchange pattern, creates a Message object from Exchange.getIn or Exchange.getOut.
|
||||
* If the exchange is out-capable then the Exchange.getOut is set, otherwise Exchange.getIn.
|
||||
*
|
||||
* @param headers additional headers to set on the created Message in addition to those
|
||||
* in the Camel message.
|
||||
*/
|
||||
def toResponseMessage(headers: Map[String, Any]): Message = responseMessage.toMessage(headers)
|
||||
|
||||
/**
|
||||
* Creates a Failure object from the adapted Exchange.
|
||||
*
|
||||
* @param headers additional headers to set on the created Message in addition to those
|
||||
* in the Camel message.
|
||||
*
|
||||
* @see Failure
|
||||
*/
|
||||
def toFailureMessage(headers: Map[String, Any]): Failure =
|
||||
Failure(exchange.getException, headers ++ responseMessage.toMessage.headers)
|
||||
|
||||
private def requestMessage = exchange.getIn
|
||||
|
||||
private def responseMessage = ExchangeHelper.getResultMessage(exchange)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapter for converting an org.apache.camel.Message to and from Message objects.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
class CamelMessageAdapter(val cm: CamelMessage) {
|
||||
/**
|
||||
* Set the adapted Camel message from the given Message object.
|
||||
*/
|
||||
def fromMessage(m: Message): CamelMessage = {
|
||||
cm.setBody(m.body)
|
||||
for (h <- m.headers) cm.getHeaders.put(h._1, h._2.asInstanceOf[AnyRef])
|
||||
cm
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Message object from the adapted Camel message.
|
||||
*/
|
||||
def toMessage: Message = toMessage(Map.empty)
|
||||
|
||||
/**
|
||||
* Creates a new Message object from the adapted Camel message.
|
||||
*
|
||||
* @param headers additional headers to set on the created Message in addition to those
|
||||
* in the Camel message.
|
||||
*/
|
||||
def toMessage(headers: Map[String, Any]): Message = Message(cm.getBody, cmHeaders(headers, cm))
|
||||
|
||||
private def cmHeaders(headers: Map[String, Any], cm: CamelMessage) =
|
||||
headers ++ MapWrapper[String, AnyRef](cm.getHeaders).elements
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines conversion methods to CamelExchangeAdapter and CamelMessageAdapter.
|
||||
* Imported by applications
|
||||
* that implicitly want to use conversion methods of CamelExchangeAdapter and CamelMessageAdapter.
|
||||
*/
|
||||
object CamelMessageConversion {
|
||||
|
||||
/**
|
||||
* Creates an CamelExchangeAdapter for the given Camel exchange.
|
||||
*/
|
||||
implicit def toExchangeAdapter(ce: Exchange): CamelExchangeAdapter =
|
||||
new CamelExchangeAdapter(ce)
|
||||
|
||||
/**
|
||||
* Creates an CamelMessageAdapter for the given Camel message.
|
||||
*/
|
||||
implicit def toMessageAdapter(cm: CamelMessage): CamelMessageAdapter =
|
||||
new CamelMessageAdapter(cm)
|
||||
}
|
||||
192
akka-camel/src/main/scala/Producer.scala
Normal file
192
akka-camel/src/main/scala/Producer.scala
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
||||
*/
|
||||
|
||||
package se.scalablesolutions.akka.camel
|
||||
|
||||
import CamelMessageConversion.toExchangeAdapter
|
||||
|
||||
import org.apache.camel.{Processor, ExchangePattern, Exchange, ProducerTemplate}
|
||||
import org.apache.camel.impl.DefaultExchange
|
||||
import org.apache.camel.spi.Synchronization
|
||||
|
||||
import se.scalablesolutions.akka.actor.Actor
|
||||
import se.scalablesolutions.akka.dispatch.CompletableFuture
|
||||
import se.scalablesolutions.akka.util.Logging
|
||||
|
||||
/**
|
||||
* Mixed in by Actor implementations that produce messages to Camel endpoints.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
trait Producer { self: Actor =>
|
||||
|
||||
private val headersToCopyDefault = Set(Message.MessageExchangeId)
|
||||
|
||||
/**
|
||||
* If set to true (default), communication with the Camel endpoint is done via the Camel
|
||||
* <a href="http://camel.apache.org/async.html">Async API</a>. Camel then processes the
|
||||
* message in a separate thread. If set to false, the actor thread is blocked until Camel
|
||||
* has finished processing the produced message.
|
||||
*/
|
||||
def async: Boolean = true
|
||||
|
||||
/**
|
||||
* If set to false (default), this producer expects a response message from the Camel endpoint.
|
||||
* If set to true, this producer communicates with the Camel endpoint with an in-only message
|
||||
* exchange pattern (fire and forget).
|
||||
*/
|
||||
def oneway: Boolean = false
|
||||
|
||||
/**
|
||||
* Returns the Camel endpoint URI to produce messages to.
|
||||
*/
|
||||
def endpointUri: String
|
||||
|
||||
/**
|
||||
* Returns the names of message headers to copy from a request message to a response message.
|
||||
* By default only the Message.MessageExchangeId is copied. Applications may override this to
|
||||
* define an application-specific set of message headers to copy.
|
||||
*/
|
||||
def headersToCopy: Set[String] = headersToCopyDefault
|
||||
|
||||
/**
|
||||
* Returns the producer template from the CamelContextManager. Applications either have to ensure
|
||||
* proper initialization of CamelContextManager or override this method.
|
||||
*
|
||||
* @see CamelContextManager.
|
||||
*/
|
||||
protected def template: ProducerTemplate = CamelContextManager.template
|
||||
|
||||
/**
|
||||
* Initiates a one-way (in-only) message exchange to the Camel endpoint given by
|
||||
* <code>endpointUri</code>. This method blocks until Camel finishes processing
|
||||
* the message exchange.
|
||||
*
|
||||
* @param msg: the message to produce. The message is converted to its canonical
|
||||
* representation via Message.canonicalize.
|
||||
*/
|
||||
protected def produceOneway(msg: Any): Unit =
|
||||
template.send(endpointUri, createInOnlyExchange.fromRequestMessage(Message.canonicalize(msg)))
|
||||
|
||||
/**
|
||||
* Initiates a one-way (in-only) message exchange to the Camel endpoint given by
|
||||
* <code>endpointUri</code>. This method triggers asynchronous processing of the
|
||||
* message exchange by Camel.
|
||||
*
|
||||
* @param msg: the message to produce. The message is converted to its canonical
|
||||
* representation via Message.canonicalize.
|
||||
*/
|
||||
protected def produceOnewayAsync(msg: Any): Unit =
|
||||
template.asyncSend(
|
||||
endpointUri, createInOnlyExchange.fromRequestMessage(Message.canonicalize(msg)))
|
||||
|
||||
/**
|
||||
* Initiates a two-way (in-out) message exchange to the Camel endpoint given by
|
||||
* <code>endpointUri</code>. This method blocks until Camel finishes processing
|
||||
* the message exchange.
|
||||
*
|
||||
* @param msg: the message to produce. The message is converted to its canonical
|
||||
* representation via Message.canonicalize.
|
||||
* @return either a response Message or a Failure object.
|
||||
*/
|
||||
protected def produce(msg: Any): Any = {
|
||||
val cmsg = Message.canonicalize(msg)
|
||||
val requestProcessor = new Processor() {
|
||||
def process(exchange: Exchange) = exchange.fromRequestMessage(cmsg)
|
||||
}
|
||||
val result = template.request(endpointUri, requestProcessor)
|
||||
if (result.isFailed) result.toFailureMessage(cmsg.headers(headersToCopy))
|
||||
else result.toResponseMessage(cmsg.headers(headersToCopy))
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates a two-way (in-out) message exchange to the Camel endpoint given by
|
||||
* <code>endpointUri</code>. This method triggers asynchronous processing of the
|
||||
* message exchange by Camel. The response message is returned asynchronously to
|
||||
* the original sender (or sender future).
|
||||
*
|
||||
* @param msg: the message to produce. The message is converted to its canonical
|
||||
* representation via Message.canonicalize.
|
||||
* @return either a response Message or a Failure object.
|
||||
* @see ProducerResponseSender
|
||||
*/
|
||||
protected def produceAsync(msg: Any): Unit = {
|
||||
val cmsg = Message.canonicalize(msg)
|
||||
val sync = new ProducerResponseSender(
|
||||
cmsg.headers(headersToCopy), this.sender, this.senderFuture, this)
|
||||
template.asyncCallback(endpointUri, createInOutExchange.fromRequestMessage(cmsg), sync)
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation for Actor.receive. Implementors may choose to
|
||||
* <code>def receive = produce</code>. This partial function calls one of
|
||||
* the protected produce methods depending on the return values of
|
||||
* <code>oneway</code> and <code>async</code>.
|
||||
*/
|
||||
protected def produce: PartialFunction[Any, Unit] = {
|
||||
case msg => {
|
||||
if ( oneway && !async) produceOneway(msg)
|
||||
else if ( oneway && async) produceOnewayAsync(msg)
|
||||
else if (!oneway && !async) reply(produce(msg))
|
||||
else /*(!oneway && async)*/ produceAsync(msg)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new in-only Exchange.
|
||||
*/
|
||||
protected def createInOnlyExchange: Exchange = createExchange(ExchangePattern.InOnly)
|
||||
|
||||
/**
|
||||
* Creates a new in-out Exchange.
|
||||
*/
|
||||
protected def createInOutExchange: Exchange = createExchange(ExchangePattern.InOut)
|
||||
|
||||
/**
|
||||
* Creates a new Exchange with given pattern from the CamelContext managed by
|
||||
* CamelContextManager. Applications either have to ensure proper initialization
|
||||
* of CamelContextManager or override this method.
|
||||
*
|
||||
* @see CamelContextManager.
|
||||
*/
|
||||
protected def createExchange(pattern: ExchangePattern): Exchange =
|
||||
new DefaultExchange(CamelContextManager.context, pattern)
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronization object that sends responses asynchronously to initial senders. This
|
||||
* class is used by Producer for asynchronous two-way messaging with a Camel endpoint.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
class ProducerResponseSender(
|
||||
headers: Map[String, Any],
|
||||
sender: Option[Actor],
|
||||
senderFuture: Option[CompletableFuture],
|
||||
producer: Actor) extends Synchronization with Logging {
|
||||
|
||||
implicit val producerActor = Some(producer) // the response sender
|
||||
|
||||
/**
|
||||
* Replies a Failure message, created from the given exchange, to <code>sender</code> (or
|
||||
* <code>senderFuture</code> if applicable).
|
||||
*/
|
||||
def onFailure(exchange: Exchange) = reply(exchange.toFailureMessage(headers))
|
||||
|
||||
/**
|
||||
* Replies a response Message, created from the given exchange, to <code>sender</code> (or
|
||||
* <code>senderFuture</code> if applicable).
|
||||
*/
|
||||
def onComplete(exchange: Exchange) = reply(exchange.toResponseMessage(headers))
|
||||
|
||||
private def reply(message: Any) = {
|
||||
sender match {
|
||||
case Some(actor) => actor ! message
|
||||
case None => senderFuture match {
|
||||
case Some(future) => future.completeWithResult(message)
|
||||
case None => log.warning("No destination for sending response")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
152
akka-camel/src/main/scala/component/ActorComponent.scala
Normal file
152
akka-camel/src/main/scala/component/ActorComponent.scala
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
||||
*/
|
||||
|
||||
package se.scalablesolutions.akka.camel.component
|
||||
|
||||
import java.lang.{RuntimeException, String}
|
||||
import java.util.{Map => JavaMap}
|
||||
import java.util.concurrent.TimeoutException
|
||||
|
||||
import org.apache.camel.{Exchange, Consumer, Processor}
|
||||
import org.apache.camel.impl.{DefaultProducer, DefaultEndpoint, DefaultComponent}
|
||||
|
||||
import se.scalablesolutions.akka.actor.{ActorRegistry, Actor}
|
||||
import se.scalablesolutions.akka.camel.{Failure, CamelMessageConversion, Message}
|
||||
|
||||
/**
|
||||
* Camel component for sending messages to and receiving replies from actors.
|
||||
*
|
||||
* @see se.scalablesolutions.akka.camel.component.ActorEndpoint
|
||||
* @see se.scalablesolutions.akka.camel.component.ActorProducer
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
class ActorComponent extends DefaultComponent {
|
||||
def createEndpoint(uri: String, remaining: String, parameters: JavaMap[String, Object]): ActorEndpoint = {
|
||||
val idAndUuid = idAndUuidPair(remaining)
|
||||
new ActorEndpoint(uri, this, idAndUuid._1, idAndUuid._2)
|
||||
}
|
||||
|
||||
private def idAndUuidPair(remaining: String): Tuple2[Option[String], Option[String]] = {
|
||||
remaining split ":" toList match {
|
||||
case id :: Nil => (Some(id), None)
|
||||
case "id" :: id :: Nil => (Some(id), None)
|
||||
case "uuid" :: uuid :: Nil => (None, Some(uuid))
|
||||
case _ => throw new IllegalArgumentException(
|
||||
"invalid path format: %s - should be <actorid> or id:<actorid> or uuid:<actoruuid>" format remaining)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Camel endpoint for referencing an actor. The actor reference is given by the endpoint URI.
|
||||
* An actor can be referenced by its <code>Actor.getId</code> or its <code>Actor.uuid</code>.
|
||||
* Supported endpoint URI formats are
|
||||
* <code>actor:<actorid></code>,
|
||||
* <code>actor:id:<actorid></code> and
|
||||
* <code>actor:uuid:<actoruuid></code>.
|
||||
*
|
||||
* @see se.scalablesolutions.akka.camel.component.ActorComponent
|
||||
* @see se.scalablesolutions.akka.camel.component.ActorProducer
|
||||
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
class ActorEndpoint(uri: String,
|
||||
comp: ActorComponent,
|
||||
val id: Option[String],
|
||||
val uuid: Option[String]) extends DefaultEndpoint(uri, comp) {
|
||||
|
||||
/**
|
||||
* @throws UnsupportedOperationException
|
||||
*/
|
||||
def createConsumer(processor: Processor): Consumer =
|
||||
throw new UnsupportedOperationException("actor consumer not supported yet")
|
||||
|
||||
/**
|
||||
* Creates a new ActorProducer instance initialized with this endpoint.
|
||||
*/
|
||||
def createProducer: ActorProducer = new ActorProducer(this)
|
||||
|
||||
/**
|
||||
* Returns true.
|
||||
*/
|
||||
def isSingleton: Boolean = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the in-message of an exchange to an actor. If the exchange pattern is out-capable,
|
||||
* the producer waits for a reply (using the !! operator), otherwise the ! operator is used
|
||||
* for sending the message.
|
||||
*
|
||||
* @see se.scalablesolutions.akka.camel.component.ActorComponent
|
||||
* @see se.scalablesolutions.akka.camel.component.ActorEndpoint
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
class ActorProducer(val ep: ActorEndpoint) extends DefaultProducer(ep) {
|
||||
import CamelMessageConversion.toExchangeAdapter
|
||||
|
||||
implicit val sender = None
|
||||
|
||||
/**
|
||||
* Depending on the exchange pattern, this method either calls processInOut or
|
||||
* processInOnly for interacting with an actor. This methods looks up the actor
|
||||
* from the ActorRegistry according to this producer's endpoint URI.
|
||||
*
|
||||
* @param exchange represents the message exchange with the actor.
|
||||
*/
|
||||
def process(exchange: Exchange) {
|
||||
val actor = target getOrElse (throw new ActorNotRegisteredException(ep.getEndpointUri))
|
||||
if (exchange.getPattern.isOutCapable) processInOut(exchange, actor)
|
||||
else processInOnly(exchange, actor)
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the exchange in-message to the given actor using the ! operator. The message
|
||||
* send to the actor is of type se.scalablesolutions.akka.camel.Message.
|
||||
*/
|
||||
protected def processInOnly(exchange: Exchange, actor: Actor): Unit =
|
||||
actor ! exchange.toRequestMessage(Map(Message.MessageExchangeId -> exchange.getExchangeId))
|
||||
|
||||
/**
|
||||
* Send the exchange in-message to the given actor using the !! operator. The exchange
|
||||
* out-message is populated from the actor's reply message. The message sent to the
|
||||
* actor is of type se.scalablesolutions.akka.camel.Message.
|
||||
*/
|
||||
protected def processInOut(exchange: Exchange, actor: Actor) {
|
||||
val header = Map(Message.MessageExchangeId -> exchange.getExchangeId)
|
||||
val result: Any = actor !! exchange.toRequestMessage(header)
|
||||
|
||||
result match {
|
||||
case Some(msg: Failure) => exchange.fromFailureMessage(msg)
|
||||
case Some(msg) => exchange.fromResponseMessage(Message.canonicalize(msg))
|
||||
case None => {
|
||||
throw new TimeoutException("timeout (%d ms) while waiting response from %s"
|
||||
format (actor.timeout, ep.getEndpointUri))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def target: Option[Actor] =
|
||||
if (ep.id.isDefined) targetById(ep.id.get)
|
||||
else targetByUuid(ep.uuid.get)
|
||||
|
||||
private def targetById(id: String) = ActorRegistry.actorsFor(id) match {
|
||||
case Nil => None
|
||||
case actor :: Nil => Some(actor)
|
||||
case actors => Some(actors.first)
|
||||
}
|
||||
|
||||
private def targetByUuid(uuid: String) = ActorRegistry.actorFor(uuid)
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown to indicate that an actor referenced by an endpoint URI cannot be
|
||||
* found in the ActorRegistry.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
class ActorNotRegisteredException(uri: String) extends RuntimeException {
|
||||
override def getMessage = "%s not registered" format uri
|
||||
}
|
||||
89
akka-camel/src/main/scala/service/CamelService.scala
Normal file
89
akka-camel/src/main/scala/service/CamelService.scala
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
||||
*/
|
||||
|
||||
package se.scalablesolutions.akka.camel.service
|
||||
|
||||
import se.scalablesolutions.akka.actor.ActorRegistry
|
||||
import se.scalablesolutions.akka.camel.CamelContextManager
|
||||
import se.scalablesolutions.akka.util.{Bootable, Logging}
|
||||
|
||||
/**
|
||||
* Used by applications (and the Kernel) to publish consumer actors via Camel
|
||||
* endpoints and to manage the life cycle of a a global CamelContext which can
|
||||
* be accessed via se.scalablesolutions.akka.camel.CamelContextManager.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
trait CamelService extends Bootable with Logging {
|
||||
|
||||
import se.scalablesolutions.akka.actor.Actor.Sender.Self
|
||||
import CamelContextManager._
|
||||
|
||||
private[camel] val consumerPublisher = new ConsumerPublisher
|
||||
private[camel] val publishRequestor = new PublishRequestor(consumerPublisher)
|
||||
|
||||
/**
|
||||
* Starts the CamelService. Any started actor that is a consumer actor will be (asynchronously)
|
||||
* published as Camel endpoint. Consumer actors that are started after this method returned will
|
||||
* be published as well. Actor publishing is done asynchronously.
|
||||
*/
|
||||
abstract override def onLoad = {
|
||||
super.onLoad
|
||||
|
||||
// Only init and start if not already done by application
|
||||
if (!initialized) init
|
||||
if (!started) start
|
||||
|
||||
// Camel should cache input streams
|
||||
context.setStreamCaching(true)
|
||||
|
||||
// start actor that exposes consumer actors via Camel endpoints
|
||||
consumerPublisher.start
|
||||
|
||||
// add listener for actor registration events
|
||||
ActorRegistry.addRegistrationListener(publishRequestor.start)
|
||||
|
||||
// publish already registered consumer actors
|
||||
for (publish <- Publish.forConsumers(ActorRegistry.actors)) consumerPublisher ! publish
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the CamelService.
|
||||
*/
|
||||
abstract override def onUnload = {
|
||||
ActorRegistry.removeRegistrationListener(publishRequestor)
|
||||
publishRequestor.stop
|
||||
consumerPublisher.stop
|
||||
stop
|
||||
super.onUnload
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the CamelService.
|
||||
*
|
||||
* @see onLoad
|
||||
*/
|
||||
def load = onLoad
|
||||
|
||||
/**
|
||||
* Stops the CamelService.
|
||||
*
|
||||
* @see onUnload
|
||||
*/
|
||||
def unload = onUnload
|
||||
}
|
||||
|
||||
/**
|
||||
* CamelService companion object used by standalone applications to create their own
|
||||
* CamelService instance.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
object CamelService {
|
||||
|
||||
/**
|
||||
* Creates a new CamelService instance.
|
||||
*/
|
||||
def newInstance: CamelService = new CamelService {}
|
||||
}
|
||||
135
akka-camel/src/main/scala/service/ConsumerPublisher.scala
Normal file
135
akka-camel/src/main/scala/service/ConsumerPublisher.scala
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
||||
*/
|
||||
package se.scalablesolutions.akka.camel.service
|
||||
|
||||
import java.io.InputStream
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
import org.apache.camel.builder.RouteBuilder
|
||||
|
||||
import se.scalablesolutions.akka.actor.{ActorUnregistered, ActorRegistered, Actor}
|
||||
import se.scalablesolutions.akka.actor.annotation.consume
|
||||
import se.scalablesolutions.akka.camel.{Consumer, CamelContextManager}
|
||||
import se.scalablesolutions.akka.util.Logging
|
||||
|
||||
/**
|
||||
* Actor that publishes consumer actors as Camel endpoints at the CamelContext managed
|
||||
* by se.scalablesolutions.akka.camel.CamelContextManager. It accepts messages of type
|
||||
* se.scalablesolutions.akka.camel.service.Publish.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
class ConsumerPublisher extends Actor with Logging {
|
||||
@volatile private var latch = new CountDownLatch(0)
|
||||
|
||||
/**
|
||||
* Adds a route to the actor identified by a Publish message to the global CamelContext.
|
||||
*/
|
||||
protected def receive = {
|
||||
case p: Publish => publish(new ConsumerRoute(p.endpointUri, p.id, p.uuid))
|
||||
case _ => { /* ignore */}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of expected Publish messages received by this actor. Used for testing
|
||||
* only.
|
||||
*/
|
||||
private[camel] def expectPublishCount(count: Int): Unit = latch = new CountDownLatch(count)
|
||||
|
||||
/**
|
||||
* Waits for the number of expected Publish messages to arrive. Used for testing only.
|
||||
*/
|
||||
private[camel] def awaitPublish = latch.await
|
||||
|
||||
private def publish(route: ConsumerRoute) {
|
||||
CamelContextManager.context.addRoutes(route)
|
||||
log.info("published actor via endpoint %s" format route.endpointUri)
|
||||
latch.countDown // needed for testing only.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the route to a consumer actor.
|
||||
*
|
||||
* @param endpointUri endpoint URI of the consumer actor
|
||||
* @param id actor identifier
|
||||
* @param uuid <code>true</code> if <code>id</code> refers to Actor.uuid, <code>false</code> if
|
||||
* <code>id</code> refers to Acotr.getId.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
class ConsumerRoute(val endpointUri: String, id: String, uuid: Boolean) extends RouteBuilder {
|
||||
// TODO: make conversions configurable
|
||||
private val bodyConversions = Map(
|
||||
"file" -> classOf[InputStream]
|
||||
)
|
||||
|
||||
def configure = {
|
||||
val schema = endpointUri take endpointUri.indexOf(":") // e.g. "http" from "http://whatever/..."
|
||||
bodyConversions.get(schema) match {
|
||||
case Some(clazz) => from(endpointUri).convertBodyTo(clazz).to(actorUri)
|
||||
case None => from(endpointUri).to(actorUri)
|
||||
}
|
||||
}
|
||||
|
||||
private def actorUri = (if (uuid) "actor:uuid:%s" else "actor:id:%s") format id
|
||||
}
|
||||
|
||||
/**
|
||||
* A registration listener that publishes consumer actors (and ignores other actors).
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
class PublishRequestor(consumerPublisher: Actor) extends Actor {
|
||||
protected def receive = {
|
||||
case ActorUnregistered(actor) => { /* ignore */ }
|
||||
case ActorRegistered(actor) => Publish.forConsumer(actor) match {
|
||||
case Some(publish) => consumerPublisher ! publish
|
||||
case None => { /* ignore */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Request message for publishing a consumer actor.
|
||||
*
|
||||
* @param endpointUri endpoint URI of the consumer actor
|
||||
* @param id actor identifier
|
||||
* @param uuid <code>true</code> if <code>id</code> refers to Actor.uuid, <code>false</code> if
|
||||
* <code>id</code> refers to Acotr.getId.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
case class Publish(endpointUri: String, id: String, uuid: Boolean)
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
object Publish {
|
||||
|
||||
/**
|
||||
* Creates a list of Publish request messages for all consumer actors in the <code>actors</code>
|
||||
* list.
|
||||
*/
|
||||
def forConsumers(actors: List[Actor]): List[Publish] =
|
||||
for (actor <- actors; pub = forConsumer(actor); if pub.isDefined) yield pub.get
|
||||
|
||||
/**
|
||||
* Creates a Publish request message if <code>actor</code> is a consumer actor.
|
||||
*/
|
||||
def forConsumer(actor: Actor): Option[Publish] =
|
||||
forConsumeAnnotated(actor) orElse forConsumerType(actor)
|
||||
|
||||
private def forConsumeAnnotated(actor: Actor): Option[Publish] = {
|
||||
val annotation = actor.getClass.getAnnotation(classOf[consume])
|
||||
if (annotation eq null) None
|
||||
else if (actor._remoteAddress.isDefined) None // do not publish proxies
|
||||
else Some(Publish(annotation.value, actor.getId, false))
|
||||
}
|
||||
|
||||
private def forConsumerType(actor: Actor): Option[Publish] =
|
||||
if (!actor.isInstanceOf[Consumer]) None
|
||||
else if (actor._remoteAddress.isDefined) None
|
||||
else Some(Publish(actor.asInstanceOf[Consumer].endpointUri, actor.uuid, true))
|
||||
}
|
||||
79
akka-camel/src/test/scala/MessageTest.scala
Normal file
79
akka-camel/src/test/scala/MessageTest.scala
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
package se.scalablesolutions.akka.camel
|
||||
|
||||
import java.io.InputStream
|
||||
|
||||
import org.apache.camel.NoTypeConversionAvailableException
|
||||
import org.junit.Assert._
|
||||
import org.scalatest.junit.JUnitSuite
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
class MessageTest extends JUnitSuite {
|
||||
|
||||
//
|
||||
// TODO: extend/rewrite unit tests
|
||||
// These tests currently only ensure proper functioning of basic features.
|
||||
//
|
||||
|
||||
@Test def shouldConvertDoubleBodyToString = {
|
||||
CamelContextManager.init
|
||||
assertEquals("1.4", Message(1.4, null).bodyAs(classOf[String]))
|
||||
}
|
||||
|
||||
@Test def shouldThrowExceptionWhenConvertingDoubleBodyToInputStream {
|
||||
CamelContextManager.init
|
||||
intercept[NoTypeConversionAvailableException] {
|
||||
Message(1.4, null).bodyAs(classOf[InputStream])
|
||||
}
|
||||
}
|
||||
|
||||
@Test def shouldReturnSubsetOfHeaders = {
|
||||
val message = Message("test" , Map("A" -> "1", "B" -> "2"))
|
||||
assertEquals(Map("B" -> "2"), message.headers(Set("B")))
|
||||
}
|
||||
|
||||
@Test def shouldTransformBodyAndPreserveHeaders = {
|
||||
assertEquals(
|
||||
Message("ab", Map("A" -> "1")),
|
||||
Message("a" , Map("A" -> "1")).transformBody[String](body => body + "b"))
|
||||
}
|
||||
|
||||
@Test def shouldConvertBodyAndPreserveHeaders = {
|
||||
CamelContextManager.init
|
||||
assertEquals(
|
||||
Message("1.4", Map("A" -> "1")),
|
||||
Message(1.4 , Map("A" -> "1")).setBodyAs(classOf[String]))
|
||||
}
|
||||
|
||||
@Test def shouldSetBodyAndPreserveHeaders = {
|
||||
assertEquals(
|
||||
Message("test2" , Map("A" -> "1")),
|
||||
Message("test1" , Map("A" -> "1")).setBody("test2"))
|
||||
}
|
||||
|
||||
@Test def shouldSetHeadersAndPreserveBody = {
|
||||
assertEquals(
|
||||
Message("test1" , Map("C" -> "3")),
|
||||
Message("test1" , Map("A" -> "1")).setHeaders(Map("C" -> "3")))
|
||||
|
||||
}
|
||||
|
||||
@Test def shouldAddHeaderAndPreserveBodyAndHeaders = {
|
||||
assertEquals(
|
||||
Message("test1" , Map("A" -> "1", "B" -> "2")),
|
||||
Message("test1" , Map("A" -> "1")).addHeader("B" -> "2"))
|
||||
}
|
||||
|
||||
@Test def shouldAddHeadersAndPreserveBodyAndHeaders = {
|
||||
assertEquals(
|
||||
Message("test1" , Map("A" -> "1", "B" -> "2")),
|
||||
Message("test1" , Map("A" -> "1")).addHeaders(Map("B" -> "2")))
|
||||
}
|
||||
|
||||
@Test def shouldRemoveHeadersAndPreserveBodyAndRemainingHeaders = {
|
||||
assertEquals(
|
||||
Message("test1" , Map("A" -> "1")),
|
||||
Message("test1" , Map("A" -> "1", "B" -> "2")).removeHeader("B"))
|
||||
}
|
||||
|
||||
}
|
||||
109
akka-camel/src/test/scala/ProducerTest.scala
Normal file
109
akka-camel/src/test/scala/ProducerTest.scala
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
package se.scalablesolutions.akka.camel
|
||||
|
||||
import org.apache.camel.{Exchange, Processor}
|
||||
import org.apache.camel.builder.RouteBuilder
|
||||
import org.apache.camel.component.mock.MockEndpoint
|
||||
import org.junit.Assert._
|
||||
import org.junit.{Test, After, Before}
|
||||
import org.scalatest.junit.JUnitSuite
|
||||
|
||||
import se.scalablesolutions.akka.actor.Actor
|
||||
|
||||
class ProducerTest extends JUnitSuite {
|
||||
|
||||
//
|
||||
// TODO: extend/rewrite unit tests
|
||||
// These tests currently only ensure proper functioning of basic features.
|
||||
//
|
||||
|
||||
import CamelContextManager._
|
||||
|
||||
var mock: MockEndpoint = _
|
||||
|
||||
@Before def setUp = {
|
||||
init
|
||||
context.addRoutes(new TestRouteBuilder)
|
||||
start
|
||||
mock = context.getEndpoint("mock:mock", classOf[MockEndpoint])
|
||||
}
|
||||
|
||||
@After def tearDown = {
|
||||
stop
|
||||
}
|
||||
|
||||
//
|
||||
// TODO: test replies to messages sent with ! (bang)
|
||||
// TODO: test copying of custom message headers
|
||||
//
|
||||
|
||||
@Test def shouldProduceMessageSyncAndReceiveResponse = {
|
||||
val producer = new TestProducer("direct:input2", false, false).start
|
||||
val message = Message("test1", Map(Message.MessageExchangeId -> "123"))
|
||||
val expected = Message("Hello test1", Map(Message.MessageExchangeId -> "123"))
|
||||
assertEquals(expected, producer !! message get)
|
||||
producer.stop
|
||||
}
|
||||
|
||||
@Test def shouldProduceMessageSyncAndReceiveFailure = {
|
||||
val producer = new TestProducer("direct:input2", false, false).start
|
||||
val message = Message("fail", Map(Message.MessageExchangeId -> "123"))
|
||||
val result = producer.!.get
|
||||
assertEquals("failure", result.cause.getMessage)
|
||||
assertEquals(Map(Message.MessageExchangeId -> "123"), result.headers)
|
||||
producer.stop
|
||||
}
|
||||
|
||||
@Test def shouldProduceMessageAsyncAndReceiveResponse = {
|
||||
val producer = new TestProducer("direct:input2", true, false).start
|
||||
val message = Message("test2", Map(Message.MessageExchangeId -> "124"))
|
||||
val expected = Message("Hello test2", Map(Message.MessageExchangeId -> "124"))
|
||||
assertEquals(expected, producer !! message get)
|
||||
producer.stop
|
||||
}
|
||||
|
||||
@Test def shouldProduceMessageAsyncAndReceiveFailure = {
|
||||
val producer = new TestProducer("direct:input2", true, false).start
|
||||
val message = Message("fail", Map(Message.MessageExchangeId -> "124"))
|
||||
val result = producer.!.get
|
||||
assertEquals("failure", result.cause.getMessage)
|
||||
assertEquals(Map(Message.MessageExchangeId -> "124"), result.headers)
|
||||
producer.stop
|
||||
}
|
||||
|
||||
@Test def shouldProduceMessageSyncWithoutReceivingResponse = {
|
||||
val producer = new TestProducer("direct:input1", false, true).start
|
||||
mock.expectedBodiesReceived("test3")
|
||||
producer.!("test3")(None)
|
||||
producer.stop
|
||||
}
|
||||
|
||||
@Test def shouldProduceMessageAsyncAndReceiveResponseSync = {
|
||||
val producer = new TestProducer("direct:input1", true, true).start
|
||||
mock.expectedBodiesReceived("test4")
|
||||
producer.!("test4")(None)
|
||||
producer.stop
|
||||
}
|
||||
|
||||
class TestProducer(uri:String, prodAsync: Boolean, prodOneway: Boolean) extends Actor with Producer {
|
||||
override def async = prodAsync
|
||||
override def oneway = prodOneway
|
||||
def endpointUri = uri
|
||||
def receive = produce
|
||||
}
|
||||
|
||||
class TestRouteBuilder extends RouteBuilder {
|
||||
def configure {
|
||||
from("direct:input1").to("mock:mock")
|
||||
from("direct:input2").process(new Processor() {
|
||||
def process(exchange: Exchange) = {
|
||||
val body = exchange.getIn.getBody
|
||||
body match {
|
||||
case "fail" => throw new Exception("failure")
|
||||
case body => exchange.getOut.setBody("Hello %s" format body)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
package se.scalablesolutions.akka.camel.component
|
||||
|
||||
import org.apache.camel.RuntimeCamelException
|
||||
import org.scalatest.{BeforeAndAfterEach, BeforeAndAfterAll, FeatureSpec}
|
||||
|
||||
import se.scalablesolutions.akka.actor.ActorRegistry
|
||||
import se.scalablesolutions.akka.camel.CamelContextManager
|
||||
import se.scalablesolutions.akka.camel.support.{Respond, Countdown, Tester, Retain}
|
||||
|
||||
class ActorComponentFeatureTest extends FeatureSpec with BeforeAndAfterAll with BeforeAndAfterEach {
|
||||
override protected def beforeAll() = {
|
||||
ActorRegistry.shutdownAll
|
||||
CamelContextManager.init
|
||||
CamelContextManager.start
|
||||
}
|
||||
|
||||
override protected def afterAll() = CamelContextManager.stop
|
||||
|
||||
override protected def afterEach() = ActorRegistry.shutdownAll
|
||||
|
||||
feature("Communicate with an actor from a Camel application using actor endpoint URIs") {
|
||||
import CamelContextManager.template
|
||||
|
||||
scenario("one-way communication using actor id") {
|
||||
val actor = new Tester with Retain with Countdown
|
||||
actor.start
|
||||
template.sendBody("actor:%s" format actor.getId, "Martin")
|
||||
assert(actor.waitFor)
|
||||
assert(actor.body === "Martin")
|
||||
}
|
||||
|
||||
scenario("one-way communication using actor uuid") {
|
||||
val actor = new Tester with Retain with Countdown
|
||||
actor.start
|
||||
template.sendBody("actor:uuid:%s" format actor.uuid, "Martin")
|
||||
assert(actor.waitFor)
|
||||
assert(actor.body === "Martin")
|
||||
}
|
||||
|
||||
scenario("two-way communication using actor id") {
|
||||
val actor = new Tester with Respond
|
||||
actor.start
|
||||
assert(template.requestBody("actor:%s" format actor.getId, "Martin") === "Hello Martin")
|
||||
}
|
||||
|
||||
scenario("two-way communication using actor uuid") {
|
||||
val actor = new Tester with Respond
|
||||
actor.start
|
||||
assert(template.requestBody("actor:uuid:%s" format actor.uuid, "Martin") === "Hello Martin")
|
||||
}
|
||||
|
||||
scenario("two-way communication with timeout") {
|
||||
val actor = new Tester {
|
||||
timeout = 1
|
||||
}
|
||||
actor.start
|
||||
intercept[RuntimeCamelException] {
|
||||
template.requestBody("actor:uuid:%s" format actor.uuid, "Martin")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
35
akka-camel/src/test/scala/component/ActorComponentTest.scala
Normal file
35
akka-camel/src/test/scala/component/ActorComponentTest.scala
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
package se.scalablesolutions.akka.camel.component
|
||||
|
||||
import org.apache.camel.impl.DefaultCamelContext
|
||||
import org.junit._
|
||||
import org.scalatest.junit.JUnitSuite
|
||||
|
||||
class ActorComponentTest extends JUnitSuite {
|
||||
|
||||
val component: ActorComponent = ActorComponentTest.mockComponent
|
||||
|
||||
@Test def shouldCreateEndpointWithIdDefined = {
|
||||
val ep1: ActorEndpoint = component.createEndpoint("actor:abc").asInstanceOf[ActorEndpoint]
|
||||
val ep2: ActorEndpoint = component.createEndpoint("actor:id:abc").asInstanceOf[ActorEndpoint]
|
||||
assert(ep1.id === Some("abc"))
|
||||
assert(ep2.id === Some("abc"))
|
||||
assert(ep1.uuid === None)
|
||||
assert(ep2.uuid === None)
|
||||
}
|
||||
|
||||
@Test def shouldCreateEndpointWithUuidDefined = {
|
||||
val ep: ActorEndpoint = component.createEndpoint("actor:uuid:abc").asInstanceOf[ActorEndpoint]
|
||||
assert(ep.uuid === Some("abc"))
|
||||
assert(ep.id === None)
|
||||
}
|
||||
}
|
||||
|
||||
object ActorComponentTest {
|
||||
def mockComponent = {
|
||||
val component = new ActorComponent
|
||||
component.setCamelContext(new DefaultCamelContext)
|
||||
component
|
||||
}
|
||||
|
||||
def mockEndpoint(uri:String) = mockComponent.createEndpoint(uri)
|
||||
}
|
||||
76
akka-camel/src/test/scala/component/ActorProducerTest.scala
Normal file
76
akka-camel/src/test/scala/component/ActorProducerTest.scala
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
package se.scalablesolutions.akka.camel.component
|
||||
|
||||
import ActorComponentTest._
|
||||
|
||||
import java.util.concurrent.TimeoutException
|
||||
|
||||
import org.apache.camel.ExchangePattern
|
||||
import org.junit.{After, Test}
|
||||
import org.scalatest.junit.JUnitSuite
|
||||
import org.scalatest.BeforeAndAfterAll
|
||||
|
||||
import se.scalablesolutions.akka.actor.ActorRegistry
|
||||
import se.scalablesolutions.akka.camel.support.{Countdown, Retain, Tester, Respond}
|
||||
import se.scalablesolutions.akka.camel.{Failure, Message}
|
||||
|
||||
class ActorProducerTest extends JUnitSuite with BeforeAndAfterAll {
|
||||
|
||||
@After def tearDown = {
|
||||
ActorRegistry.shutdownAll
|
||||
}
|
||||
|
||||
@Test def shouldSendMessageToActor = {
|
||||
val actor = new Tester with Retain with Countdown
|
||||
val endpoint = mockEndpoint("actor:uuid:%s" format actor.uuid)
|
||||
val exchange = endpoint.createExchange(ExchangePattern.InOnly)
|
||||
actor.start
|
||||
exchange.getIn.setBody("Martin")
|
||||
exchange.getIn.setHeader("k1", "v1")
|
||||
endpoint.createProducer.process(exchange)
|
||||
actor.waitFor
|
||||
assert(actor.body === "Martin")
|
||||
assert(actor.headers === Map(Message.MessageExchangeId -> exchange.getExchangeId, "k1" -> "v1"))
|
||||
}
|
||||
|
||||
@Test def shouldSendMessageToActorAndReturnResponse = {
|
||||
val actor = new Tester with Respond {
|
||||
override def response(msg: Message) = Message(super.response(msg), Map("k2" -> "v2"))
|
||||
}
|
||||
val endpoint = mockEndpoint("actor:uuid:%s" format actor.uuid)
|
||||
val exchange = endpoint.createExchange(ExchangePattern.InOut)
|
||||
actor.start
|
||||
exchange.getIn.setBody("Martin")
|
||||
exchange.getIn.setHeader("k1", "v1")
|
||||
endpoint.createProducer.process(exchange)
|
||||
assert(exchange.getOut.getBody === "Hello Martin")
|
||||
assert(exchange.getOut.getHeader("k2") === "v2")
|
||||
}
|
||||
|
||||
@Test def shouldSendMessageToActorAndReturnFailure = {
|
||||
val actor = new Tester with Respond {
|
||||
override def response(msg: Message) = Failure(new Exception("testmsg"), Map("k3" -> "v3"))
|
||||
}
|
||||
val endpoint = mockEndpoint("actor:uuid:%s" format actor.uuid)
|
||||
val exchange = endpoint.createExchange(ExchangePattern.InOut)
|
||||
actor.start
|
||||
exchange.getIn.setBody("Martin")
|
||||
exchange.getIn.setHeader("k1", "v1")
|
||||
endpoint.createProducer.process(exchange)
|
||||
assert(exchange.getException.getMessage === "testmsg")
|
||||
assert(exchange.getOut.getBody === null)
|
||||
assert(exchange.getOut.getHeader("k3") === null) // headers from failure message are currently ignored
|
||||
}
|
||||
|
||||
@Test def shouldSendMessageToActorAndTimeout: Unit = {
|
||||
val actor = new Tester {
|
||||
timeout = 1
|
||||
}
|
||||
val endpoint = mockEndpoint("actor:uuid:%s" format actor.uuid)
|
||||
val exchange = endpoint.createExchange(ExchangePattern.InOut)
|
||||
actor.start
|
||||
exchange.getIn.setBody("Martin")
|
||||
intercept[TimeoutException] {
|
||||
endpoint.createProducer.process(exchange)
|
||||
}
|
||||
}
|
||||
}
|
||||
103
akka-camel/src/test/scala/service/CamelServiceTest.scala
Normal file
103
akka-camel/src/test/scala/service/CamelServiceTest.scala
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
package se.scalablesolutions.akka.camel.service
|
||||
|
||||
import org.apache.camel.builder.RouteBuilder
|
||||
import org.junit.Assert._
|
||||
import org.scalatest.junit.JUnitSuite
|
||||
|
||||
import se.scalablesolutions.akka.actor.Actor
|
||||
import se.scalablesolutions.akka.actor.annotation.consume
|
||||
import se.scalablesolutions.akka.camel.{CamelContextManager, Consumer, Message}
|
||||
import org.junit.{Ignore, Before, After, Test}
|
||||
|
||||
class CamelServiceTest extends JUnitSuite with CamelService {
|
||||
|
||||
//
|
||||
// TODO: extend/rewrite unit tests
|
||||
// These tests currently only ensure proper functioning of basic features.
|
||||
//
|
||||
|
||||
import CamelContextManager._
|
||||
|
||||
var actor1: Actor = _
|
||||
var actor2: Actor = _
|
||||
var actor3: Actor = _
|
||||
|
||||
@Before def setUp = {
|
||||
// register actors before starting the CamelService
|
||||
actor1 = new TestActor1().start
|
||||
actor2 = new TestActor2().start
|
||||
actor3 = new TestActor3().start
|
||||
// initialize global CamelContext
|
||||
init
|
||||
// customize global CamelContext
|
||||
context.addRoutes(new TestRouteBuilder)
|
||||
consumerPublisher.expectPublishCount(2)
|
||||
load
|
||||
consumerPublisher.awaitPublish
|
||||
}
|
||||
|
||||
@After def tearDown = {
|
||||
unload
|
||||
actor1.stop
|
||||
actor2.stop
|
||||
actor3.stop
|
||||
}
|
||||
|
||||
@Test def shouldReceiveResponseViaPreStartGeneratedRoutes = {
|
||||
assertEquals("Hello Martin (actor1)", template.requestBody("direct:actor1", "Martin"))
|
||||
assertEquals("Hello Martin (actor2)", template.requestBody("direct:actor2", "Martin"))
|
||||
}
|
||||
|
||||
@Test def shouldReceiveResponseViaPostStartGeneratedRoute = {
|
||||
consumerPublisher.expectPublishCount(1)
|
||||
// register actor after starting CamelService
|
||||
val actor4 = new TestActor4().start
|
||||
consumerPublisher.awaitPublish
|
||||
assertEquals("Hello Martin (actor4)", template.requestBody("direct:actor4", "Martin"))
|
||||
actor4.stop
|
||||
}
|
||||
|
||||
@Test def shouldReceiveResponseViaCustomRoute = {
|
||||
assertEquals("Hello Tester (actor3)", template.requestBody("direct:actor3", "Martin"))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TestActor1 extends Actor with Consumer {
|
||||
def endpointUri = "direct:actor1"
|
||||
|
||||
protected def receive = {
|
||||
case msg: Message => reply("Hello %s (actor1)" format msg.body)
|
||||
}
|
||||
}
|
||||
|
||||
@consume("direct:actor2")
|
||||
class TestActor2 extends Actor {
|
||||
protected def receive = {
|
||||
case msg: Message => reply("Hello %s (actor2)" format msg.body)
|
||||
}
|
||||
}
|
||||
|
||||
class TestActor3 extends Actor {
|
||||
id = "actor3"
|
||||
|
||||
protected def receive = {
|
||||
case msg: Message => reply("Hello %s (actor3)" format msg.body)
|
||||
}
|
||||
}
|
||||
|
||||
class TestActor4 extends Actor with Consumer {
|
||||
def endpointUri = "direct:actor4"
|
||||
|
||||
protected def receive = {
|
||||
case msg: Message => reply("Hello %s (actor4)" format msg.body)
|
||||
}
|
||||
}
|
||||
|
||||
class TestRouteBuilder extends RouteBuilder {
|
||||
def configure {
|
||||
val actorUri = "actor:%s" format classOf[TestActor3].getName
|
||||
from("direct:actor3").transform(constant("Tester")).to("actor:actor3")
|
||||
}
|
||||
}
|
||||
|
||||
49
akka-camel/src/test/scala/support/TestSupport.scala
Normal file
49
akka-camel/src/test/scala/support/TestSupport.scala
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
package se.scalablesolutions.akka.camel.support
|
||||
|
||||
import java.util.concurrent.{TimeUnit, CountDownLatch}
|
||||
|
||||
import se.scalablesolutions.akka.camel.Message
|
||||
import se.scalablesolutions.akka.actor.Actor
|
||||
|
||||
trait Receive {
|
||||
def onMessage(msg: Message): Unit
|
||||
}
|
||||
|
||||
trait Respond extends Receive {self: Actor =>
|
||||
abstract override def onMessage(msg: Message): Unit = {
|
||||
super.onMessage(msg)
|
||||
reply(response(msg))
|
||||
}
|
||||
def response(msg: Message): Any = "Hello %s" format msg.body
|
||||
}
|
||||
|
||||
trait Retain extends Receive {
|
||||
var body: Any = _
|
||||
var headers = Map.empty[String, Any]
|
||||
abstract override def onMessage(msg: Message): Unit = {
|
||||
super.onMessage(msg)
|
||||
body = msg.body
|
||||
headers = msg.headers
|
||||
}
|
||||
}
|
||||
|
||||
trait Countdown extends Receive {
|
||||
val count = 1
|
||||
val duration = 5000
|
||||
val latch = new CountDownLatch(count)
|
||||
|
||||
def waitFor = latch.await(duration, TimeUnit.MILLISECONDS)
|
||||
def countDown = latch.countDown
|
||||
|
||||
abstract override def onMessage(msg: Message) = {
|
||||
super.onMessage(msg)
|
||||
countDown
|
||||
}
|
||||
}
|
||||
|
||||
class Tester extends Actor with Receive {
|
||||
def receive = {
|
||||
case msg: Message => onMessage(msg)
|
||||
}
|
||||
def onMessage(msg: Message): Unit = {}
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>akka-cluster-jgroups</artifactId>
|
||||
<name>Akka Cluster JGroups Module</name>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<artifactId>akka-cluster-parent</artifactId>
|
||||
<groupId>se.scalablesolutions.akka</groupId>
|
||||
<version>0.7-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>jgroups</groupId>
|
||||
<artifactId>jgroups</artifactId>
|
||||
<version>2.8.0.CR7</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
|
@ -1,15 +1,17 @@
|
|||
package se.scalablesolutions.akka.remote
|
||||
package se.scalablesolutions.akka.cluster.jgroups
|
||||
|
||||
import org.jgroups.{JChannel, View => JG_VIEW, Address, Message => JG_MSG, ExtendedMembershipListener, Receiver}
|
||||
|
||||
import se.scalablesolutions.akka.remote.ClusterActor._
|
||||
import se.scalablesolutions.akka.remote.BasicClusterActor
|
||||
|
||||
import org.scala_tools.javautils.Imports._
|
||||
|
||||
/**
|
||||
* Clustering support via JGroups.
|
||||
* @Author Viktor Klang
|
||||
*/
|
||||
class JGroupsClusterActor extends BasicClusterActor {
|
||||
import ClusterActor._
|
||||
import org.scala_tools.javautils.Imports._
|
||||
|
||||
type ADDR_T = Address
|
||||
|
||||
@volatile private var isActive = false
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>akka-cluster-shoal</artifactId>
|
||||
<name>Akka Cluster Shoal Module</name>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<artifactId>akka-cluster-parent</artifactId>
|
||||
<groupId>se.scalablesolutions.akka</groupId>
|
||||
<version>0.7-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<!--dependency>
|
||||
<groupId>org.glassfish</groupId>
|
||||
<artifactId>shoal</artifactId>
|
||||
<version>1.1-SNAPSHOT</version>
|
||||
</dependency-->
|
||||
<dependency>
|
||||
<groupId>shoal-jxta</groupId>
|
||||
<artifactId>shoal</artifactId>
|
||||
<version>1.1-20090818</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>shoal-jxta</groupId>
|
||||
<artifactId>jxta</artifactId>
|
||||
<version>1.1-20090818</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
|
@ -1,29 +1,16 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
||||
*/
|
||||
package se.scalablesolutions.akka.remote
|
||||
package se.scalablesolutions.akka.cluster.shoal
|
||||
|
||||
import se.scalablesolutions.akka.Config.config
|
||||
import java.util.Properties
|
||||
|
||||
import com.sun.enterprise.ee.cms.core.{CallBack,
|
||||
GMSConstants,
|
||||
GMSFactory,
|
||||
GroupManagementService,
|
||||
MessageSignal,
|
||||
Signal,
|
||||
GMSException,
|
||||
SignalAcquireException,
|
||||
SignalReleaseException,
|
||||
JoinNotificationSignal,
|
||||
FailureSuspectedSignal,
|
||||
FailureNotificationSignal }
|
||||
import com.sun.enterprise.ee.cms.impl.client.{FailureNotificationActionFactoryImpl,
|
||||
FailureSuspectedActionFactoryImpl,
|
||||
JoinNotificationActionFactoryImpl,
|
||||
MessageActionFactoryImpl,
|
||||
PlannedShutdownActionFactoryImpl
|
||||
}
|
||||
import se.scalablesolutions.akka.config.Config.config
|
||||
import se.scalablesolutions.akka.remote.{ClusterActor, BasicClusterActor, RemoteServer}
|
||||
|
||||
import com.sun.enterprise.ee.cms.core._
|
||||
import com.sun.enterprise.ee.cms.impl.client._
|
||||
|
||||
/**
|
||||
* Clustering support via Shoal.
|
||||
*/
|
||||
|
|
@ -67,9 +54,9 @@ class ShoalClusterActor extends BasicClusterActor {
|
|||
* Adds callbacks and boots up the cluster
|
||||
*/
|
||||
protected def createGMS : GroupManagementService = {
|
||||
|
||||
val g = GMSFactory.startGMSModule(serverName,name, GroupManagementService.MemberType.CORE, properties()).asInstanceOf[GroupManagementService]
|
||||
|
||||
val g = GMSFactory
|
||||
.startGMSModule(serverName,name, GroupManagementService.MemberType.CORE, properties())
|
||||
.asInstanceOf[GroupManagementService]
|
||||
val callback = createCallback
|
||||
g.addActionFactory(new JoinNotificationActionFactoryImpl(callback))
|
||||
g.addActionFactory(new FailureSuspectedActionFactoryImpl(callback))
|
||||
|
|
@ -102,8 +89,8 @@ class ShoalClusterActor extends BasicClusterActor {
|
|||
}
|
||||
signal.release()
|
||||
} catch {
|
||||
case e : SignalAcquireException => log.warning(e,"SignalAcquireException")
|
||||
case e : SignalReleaseException => log.warning(e,"SignalReleaseException")
|
||||
case e : SignalAcquireException => log.warning(e,"SignalAcquireException")
|
||||
case e : SignalReleaseException => log.warning(e,"SignalReleaseException")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>akka-cluster-tribes</artifactId>
|
||||
<name>Akka Cluster Tribes Module</name>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<artifactId>akka-cluster-parent</artifactId>
|
||||
<groupId>se.scalablesolutions.akka</groupId>
|
||||
<version>0.7-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.tomcat</groupId>
|
||||
<artifactId>tribes</artifactId>
|
||||
<version>6.0.20</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>akka-cluster-parent</artifactId>
|
||||
<name>Akka Cluster Modules</name>
|
||||
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<parent>
|
||||
<artifactId>akka</artifactId>
|
||||
<groupId>se.scalablesolutions.akka</groupId>
|
||||
<version>0.7-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modules>
|
||||
<module>akka-cluster-jgroups</module>
|
||||
<!--module>akka-cluster-tribes</module-->
|
||||
<module>akka-cluster-shoal</module>
|
||||
</modules>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<artifactId>akka-core</artifactId>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<!-- For Testing -->
|
||||
<dependency>
|
||||
<groupId>org.scalatest</groupId>
|
||||
<artifactId>scalatest</artifactId>
|
||||
<version>1.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.5</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>akka-comet</artifactId>
|
||||
<name>Akka Comet Module</name>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<artifactId>akka</artifactId>
|
||||
<groupId>se.scalablesolutions.akka</groupId>
|
||||
<version>0.7-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<!-- Core deps -->
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<artifactId>akka-rest</artifactId>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- For the Bootable -->
|
||||
<dependency>
|
||||
<groupId>com.sun.grizzly</groupId>
|
||||
<artifactId>grizzly-comet-webserver</artifactId>
|
||||
<version>${grizzly.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- For Atmosphere -->
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>servlet-api</artifactId>
|
||||
<version>2.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.atmosphere</groupId>
|
||||
<artifactId>atmosphere-annotations</artifactId>
|
||||
<version>${atmosphere.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.atmosphere</groupId>
|
||||
<artifactId>atmosphere-jersey</artifactId>
|
||||
<version>${atmosphere.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.atmosphere</groupId>
|
||||
<artifactId>atmosphere-runtime</artifactId>
|
||||
<version>${atmosphere.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
@ -4,13 +4,13 @@
|
|||
|
||||
package se.scalablesolutions.akka.comet
|
||||
|
||||
import se.scalablesolutions.akka.actor.{Actor}
|
||||
import se.scalablesolutions.akka.remote.{Cluster}
|
||||
import scala.reflect.{BeanProperty}
|
||||
import se.scalablesolutions.akka.actor.Actor
|
||||
import se.scalablesolutions.akka.remote.Cluster
|
||||
import scala.reflect.BeanProperty
|
||||
import org.atmosphere.cpr.{BroadcastFilter, ClusterBroadcastFilter, Broadcaster}
|
||||
|
||||
sealed trait ClusterCometMessageType
|
||||
case class ClusterCometBroadcast(val name : String, val msg : AnyRef) extends ClusterCometMessageType
|
||||
case class ClusterCometBroadcast(name: String, msg: AnyRef) extends ClusterCometMessageType
|
||||
|
||||
/**
|
||||
* Enables explicit clustering of Atmosphere (Comet) resources
|
||||
|
|
|
|||
|
|
@ -2,16 +2,16 @@
|
|||
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
||||
*/
|
||||
|
||||
package se.scalablesolutions.akka
|
||||
package se.scalablesolutions.akka.comet
|
||||
|
||||
import com.sun.grizzly.http.SelectorThread
|
||||
import com.sun.grizzly.http.servlet.ServletAdapter
|
||||
import com.sun.grizzly.standalone.StaticStreamAlgorithm
|
||||
|
||||
import javax.ws.rs.core.UriBuilder
|
||||
import se.scalablesolutions.akka.comet.AkkaServlet
|
||||
|
||||
import se.scalablesolutions.akka.actor.BootableActorLoaderService
|
||||
import se.scalablesolutions.akka.util.{Bootable,Logging}
|
||||
import se.scalablesolutions.akka.util.{Bootable, Logging}
|
||||
|
||||
/**
|
||||
* Handles the Akka Comet Support (load/unload)
|
||||
|
|
@ -19,16 +19,17 @@ import se.scalablesolutions.akka.util.{Bootable,Logging}
|
|||
trait BootableCometActorService extends Bootable with Logging {
|
||||
self : BootableActorLoaderService =>
|
||||
|
||||
import Config._
|
||||
import config.Config._
|
||||
|
||||
val REST_HOSTNAME = config.getString("akka.rest.hostname", "localhost")
|
||||
val REST_URL = "http://" + REST_HOSTNAME
|
||||
val REST_PORT = config.getInt("akka.rest.port", 9998)
|
||||
|
||||
protected var jerseySelectorThread: Option[SelectorThread] = None
|
||||
|
||||
abstract override def onLoad = {
|
||||
super.onLoad
|
||||
if(config.getBool("akka.rest.service", true)){
|
||||
if (config.getBool("akka.rest.service", true)) {
|
||||
|
||||
val uri = UriBuilder.fromUri(REST_URL).port(REST_PORT).build()
|
||||
|
||||
|
|
@ -42,8 +43,7 @@ trait BootableCometActorService extends Bootable with Logging {
|
|||
adapter.setHandleStaticResources(true)
|
||||
adapter.setServletInstance(new AkkaServlet)
|
||||
adapter.setContextPath(uri.getPath)
|
||||
//Using autodetection for now
|
||||
//adapter.addInitParameter("cometSupport", "org.atmosphere.container.GrizzlyCometSupport")
|
||||
adapter.addInitParameter("cometSupport", "org.atmosphere.container.GrizzlyCometSupport")
|
||||
if (HOME.isDefined) adapter.setRootFolder(HOME.get + "/deploy/root")
|
||||
log.info("REST service root path [%s] and context path [%s]", adapter.getRootFolder, adapter.getContextPath)
|
||||
|
||||
|
|
@ -19,7 +19,7 @@ import java.net.InetSocketAddress
|
|||
import java.lang.reflect.{InvocationTargetException, Method}
|
||||
|
||||
object Annotations {
|
||||
import se.scalablesolutions.akka.annotation._
|
||||
import se.scalablesolutions.akka.actor.annotation._
|
||||
val oneway = classOf[oneway]
|
||||
val transactionrequired = classOf[transactionrequired]
|
||||
val prerestart = classOf[prerestart]
|
||||
|
|
|
|||
|
|
@ -4,23 +4,25 @@
|
|||
|
||||
package se.scalablesolutions.akka.actor
|
||||
|
||||
import se.scalablesolutions.akka.Config._
|
||||
import se.scalablesolutions.akka.dispatch._
|
||||
import se.scalablesolutions.akka.config.Config._
|
||||
import se.scalablesolutions.akka.config.{AllForOneStrategy, OneForOneStrategy, FaultHandlingStrategy}
|
||||
import se.scalablesolutions.akka.config.ScalaConfig._
|
||||
import se.scalablesolutions.akka.stm.Transaction._
|
||||
import se.scalablesolutions.akka.stm.TransactionManagement._
|
||||
import se.scalablesolutions.akka.stm.{StmException, TransactionManagement}
|
||||
import se.scalablesolutions.akka.stm.TransactionManagement
|
||||
import se.scalablesolutions.akka.remote.protobuf.RemoteProtocol.RemoteRequest
|
||||
import se.scalablesolutions.akka.remote.{RemoteProtocolBuilder, RemoteClient, RemoteRequestIdFactory}
|
||||
import se.scalablesolutions.akka.serialization.Serializer
|
||||
import se.scalablesolutions.akka.util.{HashCode, Logging, UUID}
|
||||
|
||||
import org.multiverse.api.ThreadLocalTransaction._
|
||||
import org.multiverse.commitbarriers.CountDownCommitBarrier
|
||||
|
||||
import java.util.{Queue, HashSet}
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
import java.net.InetSocketAddress
|
||||
import java.util.concurrent.locks.{Lock, ReentrantLock}
|
||||
|
||||
/**
|
||||
* Implements the Transactor abstraction. E.g. a transactional actor.
|
||||
|
|
@ -72,7 +74,7 @@ object Actor extends Logging {
|
|||
val HOSTNAME = config.getString("akka.remote.server.hostname", "localhost")
|
||||
val PORT = config.getInt("akka.remote.server.port", 9999)
|
||||
|
||||
object Sender{
|
||||
object Sender {
|
||||
implicit val Self: Option[Actor] = None
|
||||
}
|
||||
|
||||
|
|
@ -98,9 +100,7 @@ object Actor extends Logging {
|
|||
* The actor is started when created.
|
||||
* Example:
|
||||
* <pre>
|
||||
* import Actor._
|
||||
*
|
||||
* val a = actor {
|
||||
* val a = Actor.init {
|
||||
* ... // init stuff
|
||||
* } receive {
|
||||
* case msg => ... // handle message
|
||||
|
|
@ -108,8 +108,8 @@ object Actor extends Logging {
|
|||
* </pre>
|
||||
*
|
||||
*/
|
||||
def actor(body: => Unit) = {
|
||||
def handler(body: => Unit) = new {
|
||||
def init[A](body: => Unit) = {
|
||||
def handler[A](body: => Unit) = new {
|
||||
def receive(handler: PartialFunction[Any, Unit]) = new Actor() {
|
||||
start
|
||||
body
|
||||
|
|
@ -198,7 +198,7 @@ object Actor extends Logging {
|
|||
*/
|
||||
trait Actor extends TransactionManagement {
|
||||
implicit protected val self: Option[Actor] = Some(this)
|
||||
implicit protected val transactionFamily: String = this.getClass.getName
|
||||
implicit protected val transactionFamilyName: String = this.getClass.getName
|
||||
|
||||
// Only mutable for RemoteServer in order to maintain identity across nodes
|
||||
private[akka] var _uuid = UUID.newUuid.toString
|
||||
|
|
@ -219,6 +219,12 @@ trait Actor extends TransactionManagement {
|
|||
private[akka] var _replyToAddress: Option[InetSocketAddress] = None
|
||||
private[akka] val _mailbox: Queue[MessageInvocation] = new ConcurrentLinkedQueue[MessageInvocation]
|
||||
|
||||
/**
|
||||
* This lock ensures thread safety in the dispatching: only one message can
|
||||
* be dispatched at once on the actor.
|
||||
*/
|
||||
private[akka] val _dispatcherLock: Lock = new ReentrantLock
|
||||
|
||||
// ====================================
|
||||
// protected fields
|
||||
// ====================================
|
||||
|
|
@ -309,9 +315,9 @@ trait Actor extends TransactionManagement {
|
|||
* If 'trapExit' is set for the actor to act as supervisor, then a faultHandler must be defined.
|
||||
* Can be one of:
|
||||
* <pre/>
|
||||
* AllForOneStrategy(maxNrOfRetries: Int, withinTimeRange: Int)
|
||||
* faultHandler = Some(AllForOneStrategy(maxNrOfRetries, withinTimeRange))
|
||||
*
|
||||
* OneForOneStrategy(maxNrOfRetries: Int, withinTimeRange: Int)
|
||||
* faultHandler = Some(OneForOneStrategy(maxNrOfRetries, withinTimeRange))
|
||||
* </pre>
|
||||
*/
|
||||
protected var faultHandler: Option[FaultHandlingStrategy] = None
|
||||
|
|
@ -334,8 +340,8 @@ trait Actor extends TransactionManagement {
|
|||
/**
|
||||
* User overridable callback/setting.
|
||||
*
|
||||
* Partial function implementing the server logic.
|
||||
* To be implemented by subclassing server.
|
||||
* Partial function implementing the actor logic.
|
||||
* To be implemented by subclassing actor.
|
||||
* <p/>
|
||||
* Example code:
|
||||
* <pre>
|
||||
|
|
@ -501,8 +507,6 @@ trait Actor extends TransactionManagement {
|
|||
def !: Option[T] = {
|
||||
if (_isKilled) throw new ActorKilledException("Actor [" + toString + "] has been killed, can't respond to messages")
|
||||
if (_isRunning) {
|
||||
val from = if (sender != null && sender.isInstanceOf[Actor]) Some(sender.asInstanceOf[Actor])
|
||||
else None
|
||||
val future = postMessageToMailboxAndCreateFutureResultWithTimeout(message, timeout, None)
|
||||
val isActiveObject = message.isInstanceOf[Invocation]
|
||||
if (isActiveObject && message.asInstanceOf[Invocation].isVoid) future.completeWithResult(None)
|
||||
|
|
@ -785,6 +789,11 @@ trait Actor extends TransactionManagement {
|
|||
}
|
||||
|
||||
protected[akka] def postMessageToMailbox(message: Any, sender: Option[Actor]): Unit = {
|
||||
if (isTransactionSetInScope) {
|
||||
log.trace("Adding transaction for %s with message [%s] to transaction set", toString, message)
|
||||
getTransactionSetInScope.incParties
|
||||
}
|
||||
|
||||
if (_remoteAddress.isDefined) {
|
||||
val requestBuilder = RemoteRequest.newBuilder
|
||||
.setId(RemoteRequestIdFactory.nextId)
|
||||
|
|
@ -796,8 +805,7 @@ trait Actor extends TransactionManagement {
|
|||
.setIsEscaped(false)
|
||||
|
||||
val id = registerSupervisorAsRemoteActor
|
||||
if(id.isDefined)
|
||||
requestBuilder.setSupervisorUuid(id.get)
|
||||
if (id.isDefined) requestBuilder.setSupervisorUuid(id.get)
|
||||
|
||||
// set the source fields used to reply back to the original sender
|
||||
// (i.e. not the remote proxy actor)
|
||||
|
|
@ -816,7 +824,7 @@ trait Actor extends TransactionManagement {
|
|||
RemoteProtocolBuilder.setMessage(message, requestBuilder)
|
||||
RemoteClient.clientFor(_remoteAddress.get).send(requestBuilder.build, None)
|
||||
} else {
|
||||
val invocation = new MessageInvocation(this, message, None, sender, currentTransaction.get)
|
||||
val invocation = new MessageInvocation(this, message, None, sender, transactionSet.get)
|
||||
if (_isEventBased) {
|
||||
_mailbox.add(invocation)
|
||||
if (_isSuspended) invocation.send
|
||||
|
|
@ -824,12 +832,18 @@ trait Actor extends TransactionManagement {
|
|||
else
|
||||
invocation.send
|
||||
}
|
||||
clearTransactionSet
|
||||
}
|
||||
|
||||
protected[akka] def postMessageToMailboxAndCreateFutureResultWithTimeout(
|
||||
message: Any,
|
||||
timeout: Long,
|
||||
senderFuture: Option[CompletableFuture]): CompletableFuture = {
|
||||
if (isTransactionSetInScope) {
|
||||
log.trace("Adding transaction for %s with message [%s] to transaction set", toString, message)
|
||||
getTransactionSetInScope.incParties
|
||||
}
|
||||
|
||||
if (_remoteAddress.isDefined) {
|
||||
val requestBuilder = RemoteRequest.newBuilder
|
||||
.setId(RemoteRequestIdFactory.nextId)
|
||||
|
|
@ -843,16 +857,18 @@ trait Actor extends TransactionManagement {
|
|||
val id = registerSupervisorAsRemoteActor
|
||||
if (id.isDefined) requestBuilder.setSupervisorUuid(id.get)
|
||||
val future = RemoteClient.clientFor(_remoteAddress.get).send(requestBuilder.build, senderFuture)
|
||||
clearTransactionSet
|
||||
if (future.isDefined) future.get
|
||||
else throw new IllegalStateException("Expected a future from remote call to actor " + toString)
|
||||
} else {
|
||||
val future = if (senderFuture.isDefined) senderFuture.get
|
||||
else new DefaultCompletableFuture(timeout)
|
||||
val invocation = new MessageInvocation(this, message, Some(future), None, currentTransaction.get)
|
||||
val invocation = new MessageInvocation(this, message, Some(future), None, transactionSet.get)
|
||||
if (_isEventBased) {
|
||||
_mailbox.add(invocation)
|
||||
invocation.send
|
||||
} else invocation.send
|
||||
clearTransactionSet
|
||||
future
|
||||
}
|
||||
}
|
||||
|
|
@ -872,7 +888,7 @@ trait Actor extends TransactionManagement {
|
|||
}
|
||||
|
||||
private def dispatch[T](messageHandle: MessageInvocation) = {
|
||||
setTransaction(messageHandle.tx)
|
||||
setTransactionSet(messageHandle.transactionSet)
|
||||
|
||||
val message = messageHandle.message //serializeMessage(messageHandle.message)
|
||||
senderFuture = messageHandle.future
|
||||
|
|
@ -894,43 +910,55 @@ trait Actor extends TransactionManagement {
|
|||
}
|
||||
|
||||
private def transactionalDispatch[T](messageHandle: MessageInvocation) = {
|
||||
setTransaction(messageHandle.tx)
|
||||
var topLevelTransaction = false
|
||||
val txSet: Option[CountDownCommitBarrier] =
|
||||
if (messageHandle.transactionSet.isDefined) messageHandle.transactionSet
|
||||
else {
|
||||
topLevelTransaction = true // FIXME create a new internal atomic block that can wait for X seconds if top level tx
|
||||
if (isTransactionRequiresNew) {
|
||||
log.trace("Creating a new transaction set (top-level transaction) \nfor actor %s \nwith message %s", toString, messageHandle)
|
||||
Some(createNewTransactionSet)
|
||||
} else None
|
||||
}
|
||||
setTransactionSet(txSet)
|
||||
|
||||
val message = messageHandle.message //serializeMessage(messageHandle.message)
|
||||
senderFuture = messageHandle.future
|
||||
sender = messageHandle.sender
|
||||
|
||||
def clearTx = {
|
||||
clearTransactionSet
|
||||
clearTransaction
|
||||
}
|
||||
|
||||
def proceed = {
|
||||
try {
|
||||
incrementTransaction
|
||||
if (base.isDefinedAt(message)) base(message) // invoke user actor's receive partial function
|
||||
else throw new IllegalArgumentException(
|
||||
"Actor " + toString + " could not process message [" + message + "]" +
|
||||
"\n\tsince no matching 'case' clause in its 'receive' method could be found")
|
||||
} finally {
|
||||
decrementTransaction
|
||||
}
|
||||
if (base.isDefinedAt(message)) base(message) // invoke user actor's receive partial function
|
||||
else throw new IllegalArgumentException(
|
||||
toString + " could not process message [" + message + "]" +
|
||||
"\n\tsince no matching 'case' clause in its 'receive' method could be found")
|
||||
setTransactionSet(txSet) // restore transaction set to allow atomic block to do commit
|
||||
}
|
||||
|
||||
try {
|
||||
if (isTransactionRequiresNew && !isTransactionInScope) {
|
||||
if (senderFuture.isEmpty) throw new StmException(
|
||||
"Can't continue transaction in a one-way fire-forget message send" +
|
||||
"\n\tE.g. using Actor '!' method or Active Object 'void' method" +
|
||||
"\n\tPlease use the Actor '!!' method or Active Object method with non-void return type")
|
||||
if (isTransactionRequiresNew) {
|
||||
atomic {
|
||||
proceed
|
||||
}
|
||||
} else proceed
|
||||
} catch {
|
||||
case e: IllegalStateException => {}
|
||||
case e =>
|
||||
// abort transaction set
|
||||
if (isTransactionSetInScope) try { getTransactionSetInScope.abort } catch { case e: IllegalStateException => {} }
|
||||
Actor.log.error(e, "Exception when invoking \n\tactor [%s] \n\twith message [%s]", this, message)
|
||||
|
||||
if (senderFuture.isDefined) senderFuture.get.completeWithException(this, e)
|
||||
clearTransaction // need to clear currentTransaction before call to supervisor
|
||||
clearTx // need to clear currentTransaction before call to supervisor
|
||||
|
||||
// FIXME to fix supervisor restart of remote actor for oneway calls, inject a supervisor proxy that can send notification back to client
|
||||
if (_supervisor.isDefined) _supervisor.get ! Exit(this, e)
|
||||
} finally {
|
||||
clearTransaction
|
||||
clearTx
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1042,6 +1070,5 @@ trait Actor extends TransactionManagement {
|
|||
that.asInstanceOf[Actor]._uuid == _uuid
|
||||
}
|
||||
|
||||
override def toString(): String = "Actor[" + id + ":" + uuid + "]"
|
||||
|
||||
override def toString = "Actor[" + id + ":" + uuid + "]"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,7 @@ import se.scalablesolutions.akka.util.Logging
|
|||
|
||||
import scala.collection.mutable.ListBuffer
|
||||
import scala.reflect.Manifest
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.{CopyOnWriteArrayList, ConcurrentHashMap}
|
||||
|
||||
/**
|
||||
* Registry holding all Actor instances in the whole system.
|
||||
|
|
@ -23,9 +22,10 @@ import java.util.concurrent.ConcurrentHashMap
|
|||
* @author <a href="http://jonasboner.com">Jonas Bonér</a>
|
||||
*/
|
||||
object ActorRegistry extends Logging {
|
||||
private val actorsByUUID = new ConcurrentHashMap[String, Actor]
|
||||
private val actorsById = new ConcurrentHashMap[String, List[Actor]]
|
||||
private val actorsByClassName = new ConcurrentHashMap[String, List[Actor]]
|
||||
private val actorsByUUID = new ConcurrentHashMap[String, Actor]
|
||||
private val actorsById = new ConcurrentHashMap[String, List[Actor]]
|
||||
private val actorsByClassName = new ConcurrentHashMap[String, List[Actor]]
|
||||
private val registrationListeners = new CopyOnWriteArrayList[Actor]
|
||||
|
||||
/**
|
||||
* Returns all actors in the system.
|
||||
|
|
@ -103,6 +103,9 @@ object ActorRegistry extends Logging {
|
|||
if (actorsByClassName.containsKey(className)) {
|
||||
actorsByClassName.put(className, actor :: actorsByClassName.get(className))
|
||||
} else actorsByClassName.put(className, actor :: Nil)
|
||||
|
||||
// notify listeners
|
||||
foreachListener(_.!(ActorRegistered(actor))(None))
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -112,6 +115,8 @@ object ActorRegistry extends Logging {
|
|||
actorsByUUID remove actor.uuid
|
||||
actorsById remove actor.getId
|
||||
actorsByClassName remove actor.getClass.getName
|
||||
// notify listeners
|
||||
foreachListener(_.!(ActorUnregistered(actor))(None))
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -125,4 +130,26 @@ object ActorRegistry extends Logging {
|
|||
actorsByClassName.clear
|
||||
log.info("All actors have been shut down and unregistered from ActorRegistry")
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the registration <code>listener</code> this this registry's listener list.
|
||||
*/
|
||||
def addRegistrationListener(listener: Actor) = {
|
||||
registrationListeners.add(listener)
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the registration <code>listener</code> this this registry's listener list.
|
||||
*/
|
||||
def removeRegistrationListener(listener: Actor) = {
|
||||
registrationListeners.remove(listener)
|
||||
}
|
||||
|
||||
private def foreachListener(f: (Actor) => Unit) {
|
||||
val iterator = registrationListeners.iterator
|
||||
while (iterator.hasNext) f(iterator.next)
|
||||
}
|
||||
}
|
||||
|
||||
case class ActorRegistered(actor: Actor)
|
||||
case class ActorUnregistered(actor: Actor)
|
||||
|
|
@ -7,8 +7,8 @@ package se.scalablesolutions.akka.actor
|
|||
import java.io.File
|
||||
import java.net.URLClassLoader
|
||||
|
||||
import se.scalablesolutions.akka.util.{Bootable,Logging}
|
||||
import se.scalablesolutions.akka.Config._
|
||||
import se.scalablesolutions.akka.util.{Bootable, Logging}
|
||||
import se.scalablesolutions.akka.config.Config._
|
||||
|
||||
/**
|
||||
* Handles all modules in the deploy directory (load and unload)
|
||||
|
|
@ -30,12 +30,8 @@ trait BootableActorLoaderService extends Bootable with Logging {
|
|||
}
|
||||
val toDeploy = for (f <- DEPLOY_DIR.listFiles().toArray.toList.asInstanceOf[List[File]]) yield f.toURL
|
||||
log.info("Deploying applications from [%s]: [%s]", DEPLOY, toDeploy.toArray.toList)
|
||||
new URLClassLoader(toDeploy.toArray, ClassLoader.getSystemClassLoader)
|
||||
} else if (getClass.getClassLoader.getResourceAsStream("akka.conf") ne null) {
|
||||
getClass.getClassLoader
|
||||
} else throw new IllegalStateException(
|
||||
"AKKA_HOME is not defined and no 'akka.conf' can be found on the classpath, aborting")
|
||||
)
|
||||
new URLClassLoader(toDeploy.toArray, getClass.getClassLoader)
|
||||
} else getClass.getClassLoader)
|
||||
}
|
||||
|
||||
abstract override def onLoad = {
|
||||
|
|
@ -47,4 +43,4 @@ trait BootableActorLoaderService extends Bootable with Logging {
|
|||
}
|
||||
|
||||
abstract override def onUnload = ActorRegistry.shutdownAll
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import java.util.concurrent._
|
|||
|
||||
import se.scalablesolutions.akka.config.ScalaConfig._
|
||||
import se.scalablesolutions.akka.config.{AllForOneStrategy, OneForOneStrategy, FaultHandlingStrategy}
|
||||
import se.scalablesolutions.akka.util.{Logging}
|
||||
import se.scalablesolutions.akka.util.Logging
|
||||
|
||||
import org.scala_tools.javautils.Imports._
|
||||
|
||||
|
|
|
|||
|
|
@ -4,231 +4,71 @@
|
|||
|
||||
package se.scalablesolutions.akka.config
|
||||
|
||||
import se.scalablesolutions.akka.actor.Actor
|
||||
import se.scalablesolutions.akka.dispatch.MessageDispatcher
|
||||
import se.scalablesolutions.akka.util.Logging
|
||||
|
||||
sealed abstract class FaultHandlingStrategy
|
||||
case class AllForOneStrategy(maxNrOfRetries: Int, withinTimeRange: Int) extends FaultHandlingStrategy
|
||||
case class OneForOneStrategy(maxNrOfRetries: Int, withinTimeRange: Int) extends FaultHandlingStrategy
|
||||
|
||||
/**
|
||||
* Configuration classes - not to be used as messages.
|
||||
*
|
||||
* @author <a href="http://jonasboner.com">Jonas Bonér</a>
|
||||
*/
|
||||
object ScalaConfig {
|
||||
sealed abstract class ConfigElement
|
||||
|
||||
abstract class Server extends ConfigElement
|
||||
abstract class FailOverScheme extends ConfigElement
|
||||
abstract class Scope extends ConfigElement
|
||||
|
||||
case class SupervisorConfig(restartStrategy: RestartStrategy, worker: List[Server]) extends Server
|
||||
|
||||
class Supervise(val actor: Actor, val lifeCycle: LifeCycle, _remoteAddress: RemoteAddress) extends Server {
|
||||
val remoteAddress: Option[RemoteAddress] = if (_remoteAddress eq null) None else Some(_remoteAddress)
|
||||
}
|
||||
object Supervise {
|
||||
def apply(actor: Actor, lifeCycle: LifeCycle, remoteAddress: RemoteAddress) = new Supervise(actor, lifeCycle, remoteAddress)
|
||||
def apply(actor: Actor, lifeCycle: LifeCycle) = new Supervise(actor, lifeCycle, null)
|
||||
def unapply(supervise: Supervise) = Some((supervise.actor, supervise.lifeCycle, supervise.remoteAddress))
|
||||
}
|
||||
|
||||
case class RestartStrategy(
|
||||
scheme: FailOverScheme,
|
||||
maxNrOfRetries: Int,
|
||||
withinTimeRange: Int,
|
||||
trapExceptions: List[Class[_ <: Throwable]]) extends ConfigElement
|
||||
|
||||
case object AllForOne extends FailOverScheme
|
||||
case object OneForOne extends FailOverScheme
|
||||
|
||||
case class LifeCycle(scope: Scope, callbacks: Option[RestartCallbacks]) extends ConfigElement
|
||||
object LifeCycle {
|
||||
def apply(scope: Scope) = new LifeCycle(scope, None)
|
||||
}
|
||||
case class RestartCallbacks(preRestart: String, postRestart: String) {
|
||||
if ((preRestart eq null) || (postRestart eq null)) throw new IllegalArgumentException("Restart callback methods can't be null")
|
||||
}
|
||||
|
||||
case object Permanent extends Scope
|
||||
case object Temporary extends Scope
|
||||
|
||||
case class RemoteAddress(val hostname: String, val port: Int) extends ConfigElement
|
||||
|
||||
class Component(_intf: Class[_],
|
||||
val target: Class[_],
|
||||
val lifeCycle: LifeCycle,
|
||||
val timeout: Int,
|
||||
val transactionRequired: Boolean,
|
||||
_dispatcher: MessageDispatcher, // optional
|
||||
_remoteAddress: RemoteAddress // optional
|
||||
) extends Server {
|
||||
val intf: Option[Class[_]] = if (_intf eq null) None else Some(_intf)
|
||||
val dispatcher: Option[MessageDispatcher] = if (_dispatcher eq null) None else Some(_dispatcher)
|
||||
val remoteAddress: Option[RemoteAddress] = if (_remoteAddress eq null) None else Some(_remoteAddress)
|
||||
}
|
||||
object Component {
|
||||
def apply(intf: Class[_], target: Class[_], lifeCycle: LifeCycle, timeout: Int) =
|
||||
new Component(intf, target, lifeCycle, timeout, false, null, null)
|
||||
|
||||
def apply(target: Class[_], lifeCycle: LifeCycle, timeout: Int) =
|
||||
new Component(null, target, lifeCycle, timeout, false, null, null)
|
||||
|
||||
def apply(intf: Class[_], target: Class[_], lifeCycle: LifeCycle, timeout: Int, dispatcher: MessageDispatcher) =
|
||||
new Component(intf, target, lifeCycle, timeout, false, dispatcher, null)
|
||||
|
||||
def apply(target: Class[_], lifeCycle: LifeCycle, timeout: Int, dispatcher: MessageDispatcher) =
|
||||
new Component(null, target, lifeCycle, timeout, false, dispatcher, null)
|
||||
|
||||
def apply(intf: Class[_], target: Class[_], lifeCycle: LifeCycle, timeout: Int, remoteAddress: RemoteAddress) =
|
||||
new Component(intf, target, lifeCycle, timeout, false, null, remoteAddress)
|
||||
|
||||
def apply(target: Class[_], lifeCycle: LifeCycle, timeout: Int, remoteAddress: RemoteAddress) =
|
||||
new Component(null, target, lifeCycle, timeout, false, null, remoteAddress)
|
||||
|
||||
def apply(intf: Class[_], target: Class[_], lifeCycle: LifeCycle, timeout: Int, dispatcher: MessageDispatcher, remoteAddress: RemoteAddress) =
|
||||
new Component(intf, target, lifeCycle, timeout, false, dispatcher, remoteAddress)
|
||||
|
||||
def apply(target: Class[_], lifeCycle: LifeCycle, timeout: Int, dispatcher: MessageDispatcher, remoteAddress: RemoteAddress) =
|
||||
new Component(null, target, lifeCycle, timeout, false, dispatcher, remoteAddress)
|
||||
|
||||
def apply(intf: Class[_], target: Class[_], lifeCycle: LifeCycle, timeout: Int, transactionRequired: Boolean) =
|
||||
new Component(intf, target, lifeCycle, timeout, transactionRequired, null, null)
|
||||
|
||||
def apply(target: Class[_], lifeCycle: LifeCycle, timeout: Int, transactionRequired: Boolean) =
|
||||
new Component(null, target, lifeCycle, timeout, transactionRequired, null, null)
|
||||
|
||||
def apply(intf: Class[_], target: Class[_], lifeCycle: LifeCycle, timeout: Int, transactionRequired: Boolean, dispatcher: MessageDispatcher) =
|
||||
new Component(intf, target, lifeCycle, timeout, transactionRequired, dispatcher, null)
|
||||
|
||||
def apply(target: Class[_], lifeCycle: LifeCycle, timeout: Int, transactionRequired: Boolean, dispatcher: MessageDispatcher) =
|
||||
new Component(null, target, lifeCycle, timeout, transactionRequired, dispatcher, null)
|
||||
|
||||
def apply(intf: Class[_], target: Class[_], lifeCycle: LifeCycle, timeout: Int, transactionRequired: Boolean, remoteAddress: RemoteAddress) =
|
||||
new Component(intf, target, lifeCycle, timeout, transactionRequired, null, remoteAddress)
|
||||
|
||||
def apply(target: Class[_], lifeCycle: LifeCycle, timeout: Int, transactionRequired: Boolean, remoteAddress: RemoteAddress) =
|
||||
new Component(null, target, lifeCycle, timeout, transactionRequired, null, remoteAddress)
|
||||
|
||||
def apply(intf: Class[_], target: Class[_], lifeCycle: LifeCycle, timeout: Int, transactionRequired: Boolean, dispatcher: MessageDispatcher, remoteAddress: RemoteAddress) =
|
||||
new Component(intf, target, lifeCycle, timeout, transactionRequired, dispatcher, remoteAddress)
|
||||
|
||||
def apply(target: Class[_], lifeCycle: LifeCycle, timeout: Int, transactionRequired: Boolean, dispatcher: MessageDispatcher, remoteAddress: RemoteAddress) =
|
||||
new Component(null, target, lifeCycle, timeout, transactionRequired, dispatcher, remoteAddress)
|
||||
}
|
||||
}
|
||||
import net.lag.configgy.{Configgy, ParseException}
|
||||
|
||||
/**
|
||||
* @author <a href="http://jonasboner.com">Jonas Bonér</a>
|
||||
*/
|
||||
object JavaConfig {
|
||||
import scala.reflect.BeanProperty
|
||||
object Config extends Logging {
|
||||
val VERSION = "0.7-SNAPSHOT"
|
||||
|
||||
sealed abstract class ConfigElement
|
||||
// Set Multiverse options for max speed
|
||||
System.setProperty("org.multiverse.MuliverseConstants.sanityChecks", "false")
|
||||
System.setProperty("org.multiverse.api.GlobalStmInstance.factorymethod", "org.multiverse.stms.alpha.AlphaStm.createFast")
|
||||
|
||||
class RestartStrategy(
|
||||
@BeanProperty val scheme: FailOverScheme,
|
||||
@BeanProperty val maxNrOfRetries: Int,
|
||||
@BeanProperty val withinTimeRange: Int,
|
||||
@BeanProperty val trapExceptions: Array[Class[_ <: Throwable]]) extends ConfigElement {
|
||||
def transform = se.scalablesolutions.akka.config.ScalaConfig.RestartStrategy(
|
||||
scheme.transform, maxNrOfRetries, withinTimeRange, trapExceptions.toList)
|
||||
val HOME = {
|
||||
val systemHome = System.getenv("AKKA_HOME")
|
||||
if (systemHome == null || systemHome.length == 0 || systemHome == ".") {
|
||||
val optionHome = System.getProperty("akka.home", "")
|
||||
if (optionHome.length != 0) Some(optionHome)
|
||||
else None
|
||||
} else Some(systemHome)
|
||||
}
|
||||
|
||||
class LifeCycle(@BeanProperty val scope: Scope, @BeanProperty val callbacks: RestartCallbacks) extends ConfigElement {
|
||||
def this(scope: Scope) = this(scope, null)
|
||||
def transform = {
|
||||
val callbackOption = if (callbacks eq null) None else Some(callbacks.transform)
|
||||
se.scalablesolutions.akka.config.ScalaConfig.LifeCycle(scope.transform, callbackOption)
|
||||
|
||||
val config = {
|
||||
if (HOME.isDefined) {
|
||||
try {
|
||||
val configFile = HOME.get + "/config/akka.conf"
|
||||
Configgy.configure(configFile)
|
||||
log.info("AKKA_HOME is defined to [%s], config loaded from [%s].", HOME.get, configFile)
|
||||
} catch {
|
||||
case e: ParseException => throw new IllegalStateException(
|
||||
"'akka.conf' config file can not be found in [" + HOME + "/config/akka.conf] aborting." +
|
||||
"\n\tEither add it in the 'config' directory or add it to the classpath.")
|
||||
}
|
||||
} else if (System.getProperty("akka.config", "") != "") {
|
||||
val configFile = System.getProperty("akka.config", "")
|
||||
try {
|
||||
Configgy.configure(configFile)
|
||||
log.info("Config loaded from -Dakka.config=%s", configFile)
|
||||
} catch {
|
||||
case e: ParseException => throw new IllegalStateException(
|
||||
"Config could not be loaded from -Dakka.config=" + configFile)
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
Configgy.configureFromResource("akka.conf", getClass.getClassLoader)
|
||||
log.info("Config loaded from the application classpath.")
|
||||
} catch {
|
||||
case e: ParseException => throw new IllegalStateException(
|
||||
"\nCan't find 'akka.conf' configuration file." +
|
||||
"\nOne of the three ways of locating the 'akka.conf' file needs to be defined:" +
|
||||
"\n\t1. Define 'AKKA_HOME' environment variable to the root of the Akka distribution." +
|
||||
"\n\t2. Define the '-Dakka.config=...' system property option." +
|
||||
"\n\t3. Put the 'akka.conf' file on the classpath." +
|
||||
"\nI have no way of finding the 'akka.conf' configuration file." +
|
||||
"\nAborting.")
|
||||
}
|
||||
}
|
||||
Configgy.config
|
||||
}
|
||||
|
||||
class RestartCallbacks(@BeanProperty val preRestart: String, @BeanProperty val postRestart: String) {
|
||||
def transform = se.scalablesolutions.akka.config.ScalaConfig.RestartCallbacks(preRestart, postRestart)
|
||||
}
|
||||
val CONFIG_VERSION = config.getString("akka.version", "0")
|
||||
if (VERSION != CONFIG_VERSION) throw new IllegalStateException(
|
||||
"Akka JAR version [" + VERSION + "] is different than the provided config ('akka.conf') version [" + CONFIG_VERSION + "]")
|
||||
val startTime = System.currentTimeMillis
|
||||
|
||||
abstract class Scope extends ConfigElement {
|
||||
def transform: se.scalablesolutions.akka.config.ScalaConfig.Scope
|
||||
}
|
||||
class Permanent extends Scope {
|
||||
override def transform = se.scalablesolutions.akka.config.ScalaConfig.Permanent
|
||||
}
|
||||
class Temporary extends Scope {
|
||||
override def transform = se.scalablesolutions.akka.config.ScalaConfig.Temporary
|
||||
}
|
||||
|
||||
abstract class FailOverScheme extends ConfigElement {
|
||||
def transform: se.scalablesolutions.akka.config.ScalaConfig.FailOverScheme
|
||||
}
|
||||
class AllForOne extends FailOverScheme {
|
||||
override def transform = se.scalablesolutions.akka.config.ScalaConfig.AllForOne
|
||||
}
|
||||
class OneForOne extends FailOverScheme {
|
||||
override def transform = se.scalablesolutions.akka.config.ScalaConfig.OneForOne
|
||||
}
|
||||
|
||||
class RemoteAddress(@BeanProperty val hostname: String, @BeanProperty val port: Int)
|
||||
|
||||
abstract class Server extends ConfigElement
|
||||
class Component(@BeanProperty val intf: Class[_],
|
||||
@BeanProperty val target: Class[_],
|
||||
@BeanProperty val lifeCycle: LifeCycle,
|
||||
@BeanProperty val timeout: Int,
|
||||
@BeanProperty val transactionRequired: Boolean, // optional
|
||||
@BeanProperty val dispatcher: MessageDispatcher, // optional
|
||||
@BeanProperty val remoteAddress: RemoteAddress // optional
|
||||
) extends Server {
|
||||
|
||||
def this(intf: Class[_], target: Class[_], lifeCycle: LifeCycle, timeout: Int) =
|
||||
this(intf, target, lifeCycle, timeout, false, null, null)
|
||||
|
||||
def this(target: Class[_], lifeCycle: LifeCycle, timeout: Int) =
|
||||
this(null, target, lifeCycle, timeout, false, null, null)
|
||||
|
||||
def this(intf: Class[_], target: Class[_], lifeCycle: LifeCycle, timeout: Int, remoteAddress: RemoteAddress) =
|
||||
this(intf, target, lifeCycle, timeout, false, null, remoteAddress)
|
||||
|
||||
def this(target: Class[_], lifeCycle: LifeCycle, timeout: Int, remoteAddress: RemoteAddress) =
|
||||
this(null, target, lifeCycle, timeout, false, null, remoteAddress)
|
||||
|
||||
def this(intf: Class[_], target: Class[_], lifeCycle: LifeCycle, timeout: Int, dispatcher: MessageDispatcher) =
|
||||
this(intf, target, lifeCycle, timeout, false, dispatcher, null)
|
||||
|
||||
def this(target: Class[_], lifeCycle: LifeCycle, timeout: Int, dispatcher: MessageDispatcher) =
|
||||
this(null, target, lifeCycle, timeout, false, dispatcher, null)
|
||||
|
||||
def this(target: Class[_], lifeCycle: LifeCycle, timeout: Int, dispatcher: MessageDispatcher, remoteAddress: RemoteAddress) =
|
||||
this(null, target, lifeCycle, timeout, false, dispatcher, remoteAddress)
|
||||
|
||||
def this(intf: Class[_], target: Class[_], lifeCycle: LifeCycle, timeout: Int, transactionRequired: Boolean) =
|
||||
this(intf, target, lifeCycle, timeout, transactionRequired, null, null)
|
||||
|
||||
def this(target: Class[_], lifeCycle: LifeCycle, timeout: Int, transactionRequired: Boolean) =
|
||||
this(null, target, lifeCycle, timeout, transactionRequired, null, null)
|
||||
|
||||
def this(intf: Class[_], target: Class[_], lifeCycle: LifeCycle, timeout: Int, transactionRequired: Boolean, remoteAddress: RemoteAddress) =
|
||||
this(intf, target, lifeCycle, timeout, transactionRequired, null, remoteAddress)
|
||||
|
||||
def this(target: Class[_], lifeCycle: LifeCycle, timeout: Int, transactionRequired: Boolean, remoteAddress: RemoteAddress) =
|
||||
this(null, target, lifeCycle, timeout, transactionRequired, null, remoteAddress)
|
||||
|
||||
def this(intf: Class[_], target: Class[_], lifeCycle: LifeCycle, timeout: Int, transactionRequired: Boolean, dispatcher: MessageDispatcher) =
|
||||
this(intf, target, lifeCycle, timeout, transactionRequired, dispatcher, null)
|
||||
|
||||
def this(target: Class[_], lifeCycle: LifeCycle, timeout: Int, transactionRequired: Boolean, dispatcher: MessageDispatcher) =
|
||||
this(null, target, lifeCycle, timeout, transactionRequired, dispatcher, null)
|
||||
|
||||
def this(target: Class[_], lifeCycle: LifeCycle, timeout: Int, transactionRequired: Boolean, dispatcher: MessageDispatcher, remoteAddress: RemoteAddress) =
|
||||
this(null, target, lifeCycle, timeout, transactionRequired, dispatcher, remoteAddress)
|
||||
|
||||
def transform =
|
||||
se.scalablesolutions.akka.config.ScalaConfig.Component(
|
||||
intf, target, lifeCycle.transform, timeout, transactionRequired, dispatcher,
|
||||
if (remoteAddress ne null) se.scalablesolutions.akka.config.ScalaConfig.RemoteAddress(remoteAddress.hostname, remoteAddress.port) else null)
|
||||
|
||||
def newSupervised(actor: Actor) =
|
||||
se.scalablesolutions.akka.config.ScalaConfig.Supervise(actor, lifeCycle.transform)
|
||||
}
|
||||
|
||||
}
|
||||
def uptime = (System.currentTimeMillis - startTime) / 1000
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ package se.scalablesolutions.akka.config
|
|||
|
||||
import scala.collection.mutable.HashSet
|
||||
|
||||
import util.Logging
|
||||
import se.scalablesolutions.akka.util.Logging
|
||||
|
||||
object ConfiguratorRepository extends Logging {
|
||||
|
||||
|
|
|
|||
234
akka-core/src/main/scala/config/SupervisionConfig.scala
Normal file
234
akka-core/src/main/scala/config/SupervisionConfig.scala
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
||||
*/
|
||||
|
||||
package se.scalablesolutions.akka.config
|
||||
|
||||
import se.scalablesolutions.akka.actor.Actor
|
||||
import se.scalablesolutions.akka.dispatch.MessageDispatcher
|
||||
|
||||
sealed abstract class FaultHandlingStrategy
|
||||
case class AllForOneStrategy(maxNrOfRetries: Int, withinTimeRange: Int) extends FaultHandlingStrategy
|
||||
case class OneForOneStrategy(maxNrOfRetries: Int, withinTimeRange: Int) extends FaultHandlingStrategy
|
||||
|
||||
/**
|
||||
* Configuration classes - not to be used as messages.
|
||||
*
|
||||
* @author <a href="http://jonasboner.com">Jonas Bonér</a>
|
||||
*/
|
||||
object ScalaConfig {
|
||||
sealed abstract class ConfigElement
|
||||
|
||||
abstract class Server extends ConfigElement
|
||||
abstract class FailOverScheme extends ConfigElement
|
||||
abstract class Scope extends ConfigElement
|
||||
|
||||
case class SupervisorConfig(restartStrategy: RestartStrategy, worker: List[Server]) extends Server
|
||||
|
||||
class Supervise(val actor: Actor, val lifeCycle: LifeCycle, _remoteAddress: RemoteAddress) extends Server {
|
||||
val remoteAddress: Option[RemoteAddress] = if (_remoteAddress eq null) None else Some(_remoteAddress)
|
||||
}
|
||||
object Supervise {
|
||||
def apply(actor: Actor, lifeCycle: LifeCycle, remoteAddress: RemoteAddress) = new Supervise(actor, lifeCycle, remoteAddress)
|
||||
def apply(actor: Actor, lifeCycle: LifeCycle) = new Supervise(actor, lifeCycle, null)
|
||||
def unapply(supervise: Supervise) = Some((supervise.actor, supervise.lifeCycle, supervise.remoteAddress))
|
||||
}
|
||||
|
||||
case class RestartStrategy(
|
||||
scheme: FailOverScheme,
|
||||
maxNrOfRetries: Int,
|
||||
withinTimeRange: Int,
|
||||
trapExceptions: List[Class[_ <: Throwable]]) extends ConfigElement
|
||||
|
||||
case object AllForOne extends FailOverScheme
|
||||
case object OneForOne extends FailOverScheme
|
||||
|
||||
case class LifeCycle(scope: Scope, callbacks: Option[RestartCallbacks]) extends ConfigElement
|
||||
object LifeCycle {
|
||||
def apply(scope: Scope) = new LifeCycle(scope, None)
|
||||
}
|
||||
case class RestartCallbacks(preRestart: String, postRestart: String) {
|
||||
if ((preRestart eq null) || (postRestart eq null)) throw new IllegalArgumentException("Restart callback methods can't be null")
|
||||
}
|
||||
|
||||
case object Permanent extends Scope
|
||||
case object Temporary extends Scope
|
||||
|
||||
case class RemoteAddress(val hostname: String, val port: Int) extends ConfigElement
|
||||
|
||||
class Component(_intf: Class[_],
|
||||
val target: Class[_],
|
||||
val lifeCycle: LifeCycle,
|
||||
val timeout: Int,
|
||||
val transactionRequired: Boolean,
|
||||
_dispatcher: MessageDispatcher, // optional
|
||||
_remoteAddress: RemoteAddress // optional
|
||||
) extends Server {
|
||||
val intf: Option[Class[_]] = if (_intf eq null) None else Some(_intf)
|
||||
val dispatcher: Option[MessageDispatcher] = if (_dispatcher eq null) None else Some(_dispatcher)
|
||||
val remoteAddress: Option[RemoteAddress] = if (_remoteAddress eq null) None else Some(_remoteAddress)
|
||||
}
|
||||
object Component {
|
||||
def apply(intf: Class[_], target: Class[_], lifeCycle: LifeCycle, timeout: Int) =
|
||||
new Component(intf, target, lifeCycle, timeout, false, null, null)
|
||||
|
||||
def apply(target: Class[_], lifeCycle: LifeCycle, timeout: Int) =
|
||||
new Component(null, target, lifeCycle, timeout, false, null, null)
|
||||
|
||||
def apply(intf: Class[_], target: Class[_], lifeCycle: LifeCycle, timeout: Int, dispatcher: MessageDispatcher) =
|
||||
new Component(intf, target, lifeCycle, timeout, false, dispatcher, null)
|
||||
|
||||
def apply(target: Class[_], lifeCycle: LifeCycle, timeout: Int, dispatcher: MessageDispatcher) =
|
||||
new Component(null, target, lifeCycle, timeout, false, dispatcher, null)
|
||||
|
||||
def apply(intf: Class[_], target: Class[_], lifeCycle: LifeCycle, timeout: Int, remoteAddress: RemoteAddress) =
|
||||
new Component(intf, target, lifeCycle, timeout, false, null, remoteAddress)
|
||||
|
||||
def apply(target: Class[_], lifeCycle: LifeCycle, timeout: Int, remoteAddress: RemoteAddress) =
|
||||
new Component(null, target, lifeCycle, timeout, false, null, remoteAddress)
|
||||
|
||||
def apply(intf: Class[_], target: Class[_], lifeCycle: LifeCycle, timeout: Int, dispatcher: MessageDispatcher, remoteAddress: RemoteAddress) =
|
||||
new Component(intf, target, lifeCycle, timeout, false, dispatcher, remoteAddress)
|
||||
|
||||
def apply(target: Class[_], lifeCycle: LifeCycle, timeout: Int, dispatcher: MessageDispatcher, remoteAddress: RemoteAddress) =
|
||||
new Component(null, target, lifeCycle, timeout, false, dispatcher, remoteAddress)
|
||||
|
||||
def apply(intf: Class[_], target: Class[_], lifeCycle: LifeCycle, timeout: Int, transactionRequired: Boolean) =
|
||||
new Component(intf, target, lifeCycle, timeout, transactionRequired, null, null)
|
||||
|
||||
def apply(target: Class[_], lifeCycle: LifeCycle, timeout: Int, transactionRequired: Boolean) =
|
||||
new Component(null, target, lifeCycle, timeout, transactionRequired, null, null)
|
||||
|
||||
def apply(intf: Class[_], target: Class[_], lifeCycle: LifeCycle, timeout: Int, transactionRequired: Boolean, dispatcher: MessageDispatcher) =
|
||||
new Component(intf, target, lifeCycle, timeout, transactionRequired, dispatcher, null)
|
||||
|
||||
def apply(target: Class[_], lifeCycle: LifeCycle, timeout: Int, transactionRequired: Boolean, dispatcher: MessageDispatcher) =
|
||||
new Component(null, target, lifeCycle, timeout, transactionRequired, dispatcher, null)
|
||||
|
||||
def apply(intf: Class[_], target: Class[_], lifeCycle: LifeCycle, timeout: Int, transactionRequired: Boolean, remoteAddress: RemoteAddress) =
|
||||
new Component(intf, target, lifeCycle, timeout, transactionRequired, null, remoteAddress)
|
||||
|
||||
def apply(target: Class[_], lifeCycle: LifeCycle, timeout: Int, transactionRequired: Boolean, remoteAddress: RemoteAddress) =
|
||||
new Component(null, target, lifeCycle, timeout, transactionRequired, null, remoteAddress)
|
||||
|
||||
def apply(intf: Class[_], target: Class[_], lifeCycle: LifeCycle, timeout: Int, transactionRequired: Boolean, dispatcher: MessageDispatcher, remoteAddress: RemoteAddress) =
|
||||
new Component(intf, target, lifeCycle, timeout, transactionRequired, dispatcher, remoteAddress)
|
||||
|
||||
def apply(target: Class[_], lifeCycle: LifeCycle, timeout: Int, transactionRequired: Boolean, dispatcher: MessageDispatcher, remoteAddress: RemoteAddress) =
|
||||
new Component(null, target, lifeCycle, timeout, transactionRequired, dispatcher, remoteAddress)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author <a href="http://jonasboner.com">Jonas Bonér</a>
|
||||
*/
|
||||
object JavaConfig {
|
||||
import scala.reflect.BeanProperty
|
||||
|
||||
sealed abstract class ConfigElement
|
||||
|
||||
class RestartStrategy(
|
||||
@BeanProperty val scheme: FailOverScheme,
|
||||
@BeanProperty val maxNrOfRetries: Int,
|
||||
@BeanProperty val withinTimeRange: Int,
|
||||
@BeanProperty val trapExceptions: Array[Class[_ <: Throwable]]) extends ConfigElement {
|
||||
def transform = se.scalablesolutions.akka.config.ScalaConfig.RestartStrategy(
|
||||
scheme.transform, maxNrOfRetries, withinTimeRange, trapExceptions.toList)
|
||||
}
|
||||
|
||||
class LifeCycle(@BeanProperty val scope: Scope, @BeanProperty val callbacks: RestartCallbacks) extends ConfigElement {
|
||||
def this(scope: Scope) = this(scope, null)
|
||||
def transform = {
|
||||
val callbackOption = if (callbacks eq null) None else Some(callbacks.transform)
|
||||
se.scalablesolutions.akka.config.ScalaConfig.LifeCycle(scope.transform, callbackOption)
|
||||
}
|
||||
}
|
||||
|
||||
class RestartCallbacks(@BeanProperty val preRestart: String, @BeanProperty val postRestart: String) {
|
||||
def transform = se.scalablesolutions.akka.config.ScalaConfig.RestartCallbacks(preRestart, postRestart)
|
||||
}
|
||||
|
||||
abstract class Scope extends ConfigElement {
|
||||
def transform: se.scalablesolutions.akka.config.ScalaConfig.Scope
|
||||
}
|
||||
class Permanent extends Scope {
|
||||
override def transform = se.scalablesolutions.akka.config.ScalaConfig.Permanent
|
||||
}
|
||||
class Temporary extends Scope {
|
||||
override def transform = se.scalablesolutions.akka.config.ScalaConfig.Temporary
|
||||
}
|
||||
|
||||
abstract class FailOverScheme extends ConfigElement {
|
||||
def transform: se.scalablesolutions.akka.config.ScalaConfig.FailOverScheme
|
||||
}
|
||||
class AllForOne extends FailOverScheme {
|
||||
override def transform = se.scalablesolutions.akka.config.ScalaConfig.AllForOne
|
||||
}
|
||||
class OneForOne extends FailOverScheme {
|
||||
override def transform = se.scalablesolutions.akka.config.ScalaConfig.OneForOne
|
||||
}
|
||||
|
||||
class RemoteAddress(@BeanProperty val hostname: String, @BeanProperty val port: Int)
|
||||
|
||||
abstract class Server extends ConfigElement
|
||||
class Component(@BeanProperty val intf: Class[_],
|
||||
@BeanProperty val target: Class[_],
|
||||
@BeanProperty val lifeCycle: LifeCycle,
|
||||
@BeanProperty val timeout: Int,
|
||||
@BeanProperty val transactionRequired: Boolean, // optional
|
||||
@BeanProperty val dispatcher: MessageDispatcher, // optional
|
||||
@BeanProperty val remoteAddress: RemoteAddress // optional
|
||||
) extends Server {
|
||||
|
||||
def this(intf: Class[_], target: Class[_], lifeCycle: LifeCycle, timeout: Int) =
|
||||
this(intf, target, lifeCycle, timeout, false, null, null)
|
||||
|
||||
def this(target: Class[_], lifeCycle: LifeCycle, timeout: Int) =
|
||||
this(null, target, lifeCycle, timeout, false, null, null)
|
||||
|
||||
def this(intf: Class[_], target: Class[_], lifeCycle: LifeCycle, timeout: Int, remoteAddress: RemoteAddress) =
|
||||
this(intf, target, lifeCycle, timeout, false, null, remoteAddress)
|
||||
|
||||
def this(target: Class[_], lifeCycle: LifeCycle, timeout: Int, remoteAddress: RemoteAddress) =
|
||||
this(null, target, lifeCycle, timeout, false, null, remoteAddress)
|
||||
|
||||
def this(intf: Class[_], target: Class[_], lifeCycle: LifeCycle, timeout: Int, dispatcher: MessageDispatcher) =
|
||||
this(intf, target, lifeCycle, timeout, false, dispatcher, null)
|
||||
|
||||
def this(target: Class[_], lifeCycle: LifeCycle, timeout: Int, dispatcher: MessageDispatcher) =
|
||||
this(null, target, lifeCycle, timeout, false, dispatcher, null)
|
||||
|
||||
def this(target: Class[_], lifeCycle: LifeCycle, timeout: Int, dispatcher: MessageDispatcher, remoteAddress: RemoteAddress) =
|
||||
this(null, target, lifeCycle, timeout, false, dispatcher, remoteAddress)
|
||||
|
||||
def this(intf: Class[_], target: Class[_], lifeCycle: LifeCycle, timeout: Int, transactionRequired: Boolean) =
|
||||
this(intf, target, lifeCycle, timeout, transactionRequired, null, null)
|
||||
|
||||
def this(target: Class[_], lifeCycle: LifeCycle, timeout: Int, transactionRequired: Boolean) =
|
||||
this(null, target, lifeCycle, timeout, transactionRequired, null, null)
|
||||
|
||||
def this(intf: Class[_], target: Class[_], lifeCycle: LifeCycle, timeout: Int, transactionRequired: Boolean, remoteAddress: RemoteAddress) =
|
||||
this(intf, target, lifeCycle, timeout, transactionRequired, null, remoteAddress)
|
||||
|
||||
def this(target: Class[_], lifeCycle: LifeCycle, timeout: Int, transactionRequired: Boolean, remoteAddress: RemoteAddress) =
|
||||
this(null, target, lifeCycle, timeout, transactionRequired, null, remoteAddress)
|
||||
|
||||
def this(intf: Class[_], target: Class[_], lifeCycle: LifeCycle, timeout: Int, transactionRequired: Boolean, dispatcher: MessageDispatcher) =
|
||||
this(intf, target, lifeCycle, timeout, transactionRequired, dispatcher, null)
|
||||
|
||||
def this(target: Class[_], lifeCycle: LifeCycle, timeout: Int, transactionRequired: Boolean, dispatcher: MessageDispatcher) =
|
||||
this(null, target, lifeCycle, timeout, transactionRequired, dispatcher, null)
|
||||
|
||||
def this(target: Class[_], lifeCycle: LifeCycle, timeout: Int, transactionRequired: Boolean, dispatcher: MessageDispatcher, remoteAddress: RemoteAddress) =
|
||||
this(null, target, lifeCycle, timeout, transactionRequired, dispatcher, remoteAddress)
|
||||
|
||||
def transform =
|
||||
se.scalablesolutions.akka.config.ScalaConfig.Component(
|
||||
intf, target, lifeCycle.transform, timeout, transactionRequired, dispatcher,
|
||||
if (remoteAddress ne null) se.scalablesolutions.akka.config.ScalaConfig.RemoteAddress(remoteAddress.hostname, remoteAddress.port) else null)
|
||||
|
||||
def newSupervised(actor: Actor) =
|
||||
se.scalablesolutions.akka.config.ScalaConfig.Supervise(actor, lifeCycle.transform)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -57,18 +57,29 @@ class ExecutorBasedEventDrivenDispatcher(_name: String) extends MessageDispatche
|
|||
@volatile private var active: Boolean = false
|
||||
|
||||
val name: String = "event-driven:executor:dispatcher:" + _name
|
||||
init
|
||||
|
||||
init
|
||||
|
||||
def dispatch(invocation: MessageInvocation) = if (active) {
|
||||
executor.execute(new Runnable() {
|
||||
def run = {
|
||||
invocation.receiver.synchronized {
|
||||
var messageInvocation = invocation.receiver._mailbox.poll
|
||||
while (messageInvocation != null) {
|
||||
messageInvocation.invoke
|
||||
messageInvocation = invocation.receiver._mailbox.poll
|
||||
var lockAcquiredOnce = false
|
||||
// this do-wile loop is required to prevent missing new messages between the end of the inner while
|
||||
// loop and releasing the lock
|
||||
do {
|
||||
if (invocation.receiver._dispatcherLock.tryLock) {
|
||||
lockAcquiredOnce = true
|
||||
try {
|
||||
// Only dispatch if we got the lock. Otherwise another thread is already dispatching.
|
||||
var messageInvocation = invocation.receiver._mailbox.poll
|
||||
while (messageInvocation != null) {
|
||||
messageInvocation.invoke
|
||||
messageInvocation = invocation.receiver._mailbox.poll
|
||||
}
|
||||
} finally {
|
||||
invocation.receiver._dispatcherLock.unlock
|
||||
}
|
||||
}
|
||||
}
|
||||
} while ((lockAcquiredOnce && !invocation.receiver._mailbox.isEmpty))
|
||||
}
|
||||
})
|
||||
} else throw new IllegalStateException("Can't submit invocations to dispatcher since it's not started")
|
||||
|
|
@ -88,4 +99,4 @@ class ExecutorBasedEventDrivenDispatcher(_name: String) extends MessageDispatche
|
|||
"Can't build a new thread pool for a dispatcher that is already up and running")
|
||||
|
||||
private[akka] def init = withNewThreadPoolWithLinkedBlockingQueueWithUnboundedCapacity.buildThreadPool
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ class FutureTimeoutException(message: String) extends RuntimeException(message)
|
|||
object Futures {
|
||||
|
||||
/**
|
||||
* FIXME document
|
||||
* <pre>
|
||||
* val future = Futures.future(1000) {
|
||||
* ... // do stuff
|
||||
|
|
|
|||
|
|
@ -7,16 +7,17 @@ package se.scalablesolutions.akka.dispatch
|
|||
import java.util.List
|
||||
|
||||
import se.scalablesolutions.akka.util.{HashCode, Logging}
|
||||
import se.scalablesolutions.akka.stm.Transaction
|
||||
import se.scalablesolutions.akka.actor.Actor
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
import org.multiverse.commitbarriers.CountDownCommitBarrier
|
||||
|
||||
final class MessageInvocation(val receiver: Actor,
|
||||
val message: Any,
|
||||
val future: Option[CompletableFuture],
|
||||
val sender: Option[Actor],
|
||||
val tx: Option[Transaction]) {
|
||||
val transactionSet: Option[CountDownCommitBarrier]) {
|
||||
if (receiver eq null) throw new IllegalArgumentException("receiver is null")
|
||||
|
||||
def invoke = receiver.invoke(this)
|
||||
|
|
@ -37,13 +38,13 @@ final class MessageInvocation(val receiver: Actor,
|
|||
that.asInstanceOf[MessageInvocation].message == message
|
||||
}
|
||||
|
||||
override def toString(): String = synchronized {
|
||||
override def toString = synchronized {
|
||||
"MessageInvocation[" +
|
||||
"\n\tmessage = " + message +
|
||||
"\n\treceiver = " + receiver +
|
||||
"\n\tsender = " + sender +
|
||||
"\n\tfuture = " + future +
|
||||
"\n\ttx = " + tx +
|
||||
"\n\ttransactionSet = " + transactionSet +
|
||||
"\n]"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@
|
|||
package se.scalablesolutions.akka.remote
|
||||
|
||||
import se.scalablesolutions.akka.actor.BootableActorLoaderService
|
||||
import se.scalablesolutions.akka.util.{Bootable,Logging}
|
||||
import se.scalablesolutions.akka.Config.config
|
||||
import se.scalablesolutions.akka.util.{Bootable, Logging}
|
||||
import se.scalablesolutions.akka.config.Config.config
|
||||
|
||||
/**
|
||||
* This bundle/service is responsible for booting up and shutting down the remote actors facility
|
||||
|
|
@ -23,22 +23,19 @@ trait BootableRemoteActorService extends Bootable with Logging {
|
|||
def startRemoteService = remoteServerThread.start
|
||||
|
||||
abstract override def onLoad = {
|
||||
super.onLoad //Initialize BootableActorLoaderService before remote service
|
||||
if(config.getBool("akka.remote.server.service", true)){
|
||||
log.info("Starting up Cluster Service")
|
||||
Cluster.start
|
||||
super.onLoad //Initialize BootableActorLoaderService before remote service
|
||||
|
||||
if(config.getBool("akka.remote.cluster.service", true))
|
||||
Cluster.start(self.applicationLoader)
|
||||
|
||||
log.info("Initializing Remote Actors Service...")
|
||||
startRemoteService
|
||||
log.info("Remote Actors Service initialized!")
|
||||
}
|
||||
else
|
||||
super.onLoad
|
||||
|
||||
}
|
||||
|
||||
abstract override def onUnload = {
|
||||
super.onUnload
|
||||
|
||||
log.info("Shutting down Remote Actors Service")
|
||||
|
||||
RemoteNode.shutdown
|
||||
|
|
@ -50,6 +47,8 @@ trait BootableRemoteActorService extends Bootable with Logging {
|
|||
Cluster.shutdown
|
||||
|
||||
log.info("Remote Actors Service has been shut down")
|
||||
|
||||
super.onUnload
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
package se.scalablesolutions.akka.remote
|
||||
|
||||
import se.scalablesolutions.akka.Config.config
|
||||
import se.scalablesolutions.akka.config.Config.config
|
||||
import se.scalablesolutions.akka.config.ScalaConfig._
|
||||
import se.scalablesolutions.akka.serialization.Serializer
|
||||
import se.scalablesolutions.akka.actor.{Supervisor, SupervisorFactory, Actor, ActorRegistry}
|
||||
|
|
@ -17,17 +17,43 @@ import scala.collection.immutable.{Map, HashMap}
|
|||
* @author Viktor Klang
|
||||
*/
|
||||
trait Cluster {
|
||||
|
||||
/**
|
||||
* Specifies the cluster name
|
||||
*/
|
||||
def name: String
|
||||
|
||||
/**
|
||||
* Adds the specified hostname + port as a local node
|
||||
* This information will be propagated to other nodes in the cluster
|
||||
* and will be available at the other nodes through lookup and foreach
|
||||
*/
|
||||
def registerLocalNode(hostname: String, port: Int): Unit
|
||||
|
||||
/**
|
||||
* Removes the specified hostname + port from the local node
|
||||
* This information will be propagated to other nodes in the cluster
|
||||
* and will no longer be available at the other nodes through lookup and foreach
|
||||
*/
|
||||
def deregisterLocalNode(hostname: String, port: Int): Unit
|
||||
|
||||
/**
|
||||
* Sends the message to all Actors of the specified type on all other nodes in the cluster
|
||||
*/
|
||||
def relayMessage(to: Class[_ <: Actor], msg: AnyRef): Unit
|
||||
|
||||
/**
|
||||
* Traverses all known remote addresses avaiable at all other nodes in the cluster
|
||||
* and applies the given PartialFunction on the first address that it's defined at
|
||||
* The order of application is undefined and may vary
|
||||
*/
|
||||
def lookup[T](pf: PartialFunction[RemoteAddress, T]): Option[T]
|
||||
|
||||
def foreach(f : (RemoteAddress) => Unit) : Unit
|
||||
|
||||
/**
|
||||
* Applies the specified function to all known remote addresses on al other nodes in the cluster
|
||||
* The order of application is undefined and may vary
|
||||
*/
|
||||
def foreach(f: (RemoteAddress) => Unit): Unit
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -37,6 +63,10 @@ trait Cluster {
|
|||
*/
|
||||
trait ClusterActor extends Actor with Cluster {
|
||||
val name = config.getString("akka.remote.cluster.name") getOrElse "default"
|
||||
|
||||
@volatile protected var serializer : Serializer = _
|
||||
|
||||
private[remote] def setSerializer(s : Serializer) : Unit = serializer = s
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -44,20 +74,20 @@ trait ClusterActor extends Actor with Cluster {
|
|||
*
|
||||
* @author Viktor Klang
|
||||
*/
|
||||
private[remote] object ClusterActor {
|
||||
private[akka] object ClusterActor {
|
||||
sealed trait ClusterMessage
|
||||
|
||||
private[remote] case class RelayedMessage(actorClassFQN: String, msg: AnyRef) extends ClusterMessage
|
||||
private[remote] case class Message[ADDR_T](sender : ADDR_T,msg : Array[Byte])
|
||||
private[remote] case object PapersPlease extends ClusterMessage
|
||||
private[remote] case class Papers(addresses: List[RemoteAddress]) extends ClusterMessage
|
||||
private[remote] case object Block extends ClusterMessage
|
||||
private[remote] case object Unblock extends ClusterMessage
|
||||
private[remote] case class View[ADDR_T](othersPresent : Set[ADDR_T]) extends ClusterMessage
|
||||
private[remote] case class Zombie[ADDR_T](address: ADDR_T) extends ClusterMessage
|
||||
private[remote] case class RegisterLocalNode(server: RemoteAddress) extends ClusterMessage
|
||||
private[remote] case class DeregisterLocalNode(server: RemoteAddress) extends ClusterMessage
|
||||
private[remote] case class Node(endpoints: List[RemoteAddress])
|
||||
private[akka] case class RelayedMessage(actorClassFQN: String, msg: AnyRef) extends ClusterMessage
|
||||
private[akka] case class Message[ADDR_T](sender: ADDR_T, msg: Array[Byte])
|
||||
private[akka] case object PapersPlease extends ClusterMessage
|
||||
private[akka] case class Papers(addresses: List[RemoteAddress]) extends ClusterMessage
|
||||
private[akka] case object Block extends ClusterMessage
|
||||
private[akka] case object Unblock extends ClusterMessage
|
||||
private[akka] case class View[ADDR_T](othersPresent: Set[ADDR_T]) extends ClusterMessage
|
||||
private[akka] case class Zombie[ADDR_T](address: ADDR_T) extends ClusterMessage
|
||||
private[akka] case class RegisterLocalNode(server: RemoteAddress) extends ClusterMessage
|
||||
private[akka] case class DeregisterLocalNode(server: RemoteAddress) extends ClusterMessage
|
||||
private[akka] case class Node(endpoints: List[RemoteAddress])
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -67,72 +97,70 @@ private[remote] object ClusterActor {
|
|||
*/
|
||||
abstract class BasicClusterActor extends ClusterActor {
|
||||
import ClusterActor._
|
||||
|
||||
type ADDR_T
|
||||
|
||||
|
||||
@volatile private var local: Node = Node(Nil)
|
||||
@volatile private var remotes: Map[ADDR_T, Node] = Map()
|
||||
|
||||
override def init = {
|
||||
remotes = new HashMap[ADDR_T, Node]
|
||||
remotes = new HashMap[ADDR_T, Node]
|
||||
}
|
||||
|
||||
override def shutdown = {
|
||||
remotes = Map()
|
||||
remotes = Map()
|
||||
}
|
||||
|
||||
def receive = {
|
||||
case v : View[ADDR_T] => {
|
||||
case v: View[ADDR_T] => {
|
||||
// Not present in the cluster anymore = presumably zombies
|
||||
// Nodes we have no prior knowledge existed = unknowns
|
||||
val zombies = Set[ADDR_T]() ++ remotes.keySet -- v.othersPresent
|
||||
val unknown = v.othersPresent -- remotes.keySet
|
||||
|
||||
log debug ("Updating view")
|
||||
log debug ("Other memebers: [%s]",v.othersPresent)
|
||||
log debug ("Zombies: [%s]",zombies)
|
||||
log debug ("Unknowns: [%s]",unknown)
|
||||
log debug ("Other memebers: [%s]", v.othersPresent)
|
||||
log debug ("Zombies: [%s]", zombies)
|
||||
log debug ("Unknowns: [%s]", unknown)
|
||||
|
||||
// Tell the zombies and unknowns to provide papers and prematurely treat the zombies as dead
|
||||
broadcast(zombies ++ unknown, PapersPlease)
|
||||
remotes = remotes -- zombies
|
||||
}
|
||||
|
||||
case z : Zombie[ADDR_T] => { //Ask the presumed zombie for papers and prematurely treat it as dead
|
||||
case z: Zombie[ADDR_T] => { //Ask the presumed zombie for papers and prematurely treat it as dead
|
||||
log debug ("Killing Zombie Node: %s", z.address)
|
||||
broadcast(z.address :: Nil, PapersPlease)
|
||||
remotes = remotes - z.address
|
||||
}
|
||||
|
||||
case rm @ RelayedMessage(_, _) => {
|
||||
case rm@RelayedMessage(_, _) => {
|
||||
log debug ("Relaying message: %s", rm)
|
||||
broadcast(rm)
|
||||
}
|
||||
|
||||
case m : Message[ADDR_T] => {
|
||||
val (src,msg) = (m.sender,m.msg)
|
||||
(Cluster.serializer in (msg, None)) match {
|
||||
case m: Message[ADDR_T] => {
|
||||
val (src, msg) = (m.sender, m.msg)
|
||||
(serializer in (msg, None)) match {
|
||||
|
||||
case PapersPlease => {
|
||||
log debug ("Asked for papers by %s", src)
|
||||
broadcast(src :: Nil, Papers(local.endpoints))
|
||||
case PapersPlease => {
|
||||
log debug ("Asked for papers by %s", src)
|
||||
broadcast(src :: Nil, Papers(local.endpoints))
|
||||
|
||||
if (remotes.get(src).isEmpty) // If we were asked for papers from someone we don't know, ask them!
|
||||
broadcast(src :: Nil, PapersPlease)
|
||||
}
|
||||
|
||||
case Papers(x) => remotes = remotes + (src -> Node(x))
|
||||
|
||||
case RelayedMessage(c, m) => ActorRegistry.actorsFor(c).foreach(_ send m)
|
||||
|
||||
case unknown => log debug ("Unknown message: %s", unknown.toString)
|
||||
if (remotes.get(src).isEmpty) // If we were asked for papers from someone we don't know, ask them!
|
||||
broadcast(src :: Nil, PapersPlease)
|
||||
}
|
||||
|
||||
case Papers(x) => remotes = remotes + (src -> Node(x))
|
||||
|
||||
case RelayedMessage(c, m) => ActorRegistry.actorsFor(c).foreach(_ send m)
|
||||
|
||||
case unknown => log debug ("Unknown message: %s", unknown.toString)
|
||||
}
|
||||
}
|
||||
|
||||
case RegisterLocalNode(s) => {
|
||||
log debug ("RegisterLocalNode: %s", s)
|
||||
local = Node(local.endpoints + s)
|
||||
local = Node(s :: local.endpoints)
|
||||
broadcast(Papers(local.endpoints))
|
||||
}
|
||||
|
||||
|
|
@ -146,20 +174,20 @@ abstract class BasicClusterActor extends ClusterActor {
|
|||
/**
|
||||
* Implement this in a subclass to add node-to-node messaging
|
||||
*/
|
||||
protected def toOneNode(dest : ADDR_T, msg : Array[Byte]) : Unit
|
||||
protected def toOneNode(dest: ADDR_T, msg: Array[Byte]): Unit
|
||||
|
||||
/**
|
||||
* Implement this in a subclass to add node-to-many-nodes messaging
|
||||
*/
|
||||
protected def toAllNodes(msg : Array[Byte]) : Unit
|
||||
protected def toAllNodes(msg: Array[Byte]): Unit
|
||||
|
||||
/**
|
||||
* Sends the specified message to the given recipients using the serializer
|
||||
* that's been set in the akka-conf
|
||||
*/
|
||||
protected def broadcast[T <: AnyRef](recipients: Iterable[ADDR_T], msg: T): Unit = {
|
||||
lazy val m = Cluster.serializer out msg
|
||||
for (r <- recipients) toOneNode(r,m)
|
||||
lazy val m = serializer out msg
|
||||
for (r <- recipients) toOneNode(r, m)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -167,18 +195,18 @@ abstract class BasicClusterActor extends ClusterActor {
|
|||
* that's been set in the akka-conf
|
||||
*/
|
||||
protected def broadcast[T <: AnyRef](msg: T): Unit =
|
||||
if (!remotes.isEmpty) toAllNodes(Cluster.serializer out msg)
|
||||
if (!remotes.isEmpty) toAllNodes(serializer out msg)
|
||||
|
||||
/**
|
||||
* Applies the given PartialFunction to all known RemoteAddresses
|
||||
*/
|
||||
def lookup[T](handleRemoteAddress: PartialFunction[RemoteAddress, T]): Option[T] =
|
||||
remotes.values.toList.flatMap(_.endpoints).find(handleRemoteAddress isDefinedAt _).map(handleRemoteAddress)
|
||||
|
||||
|
||||
/**
|
||||
* Applies the given function to all remote addresses known
|
||||
*/
|
||||
def foreach(f : (RemoteAddress) => Unit) : Unit = remotes.values.toList.flatMap(_.endpoints).foreach(f)
|
||||
def foreach(f: (RemoteAddress) => Unit): Unit = remotes.values.toList.flatMap(_.endpoints).foreach(f)
|
||||
|
||||
/**
|
||||
* Registers a local endpoint
|
||||
|
|
@ -205,28 +233,31 @@ abstract class BasicClusterActor extends ClusterActor {
|
|||
* Loads a specified ClusterActor and delegates to that instance.
|
||||
*/
|
||||
object Cluster extends Cluster with Logging {
|
||||
@volatile private[remote] var clusterActor: Option[ClusterActor] = None
|
||||
@volatile private[remote] var supervisor: Option[Supervisor] = None
|
||||
|
||||
private[remote] lazy val serializer: Serializer = {
|
||||
val className = config.getString("akka.remote.cluster.serializer", Serializer.Java.getClass.getName)
|
||||
Class.forName(className).newInstance.asInstanceOf[Serializer]
|
||||
}
|
||||
lazy val DEFAULT_SERIALIZER_CLASS_NAME = Serializer.Java.getClass.getName
|
||||
|
||||
private[remote] def createClusterActor : Option[ClusterActor] = {
|
||||
@volatile private[remote] var clusterActor: Option[ClusterActor] = None
|
||||
|
||||
private[remote] def createClusterActor(loader : ClassLoader): Option[ClusterActor] = {
|
||||
val name = config.getString("akka.remote.cluster.actor")
|
||||
|
||||
if (name.isEmpty) throw new IllegalArgumentException(
|
||||
"Can't start cluster since the 'akka.remote.cluster.actor' configuration option is not defined")
|
||||
|
||||
val serializer = Class.forName(config.getString("akka.remote.cluster.serializer", DEFAULT_SERIALIZER_CLASS_NAME)).newInstance.asInstanceOf[Serializer]
|
||||
serializer.classLoader = Some(loader)
|
||||
try {
|
||||
name map { fqn =>
|
||||
Class.forName(fqn).newInstance.asInstanceOf[ClusterActor]
|
||||
name map {
|
||||
fqn =>
|
||||
val a = Class.forName(fqn).newInstance.asInstanceOf[ClusterActor]
|
||||
a setSerializer serializer
|
||||
a
|
||||
}
|
||||
}
|
||||
catch {
|
||||
case e => log.error(e,"Couldn't load Cluster provider: [%s]",name.getOrElse("Not specified")); None
|
||||
case e => log.error(e, "Couldn't load Cluster provider: [%s]", name.getOrElse("Not specified")); None
|
||||
}
|
||||
}
|
||||
|
||||
private[remote] def createSupervisor(actor : ClusterActor) : Option[Supervisor] = {
|
||||
private[akka] def createSupervisor(actor: ClusterActor): Option[Supervisor] = {
|
||||
val sup = SupervisorFactory(
|
||||
SupervisorConfig(
|
||||
RestartStrategy(OneForOne, 5, 1000, List(classOf[Exception])),
|
||||
|
|
@ -245,23 +276,28 @@ object Cluster extends Cluster with Logging {
|
|||
def deregisterLocalNode(hostname: String, port: Int): Unit = clusterActor.foreach(_.deregisterLocalNode(hostname, port))
|
||||
|
||||
def relayMessage(to: Class[_ <: Actor], msg: AnyRef): Unit = clusterActor.foreach(_.relayMessage(to, msg))
|
||||
|
||||
def foreach(f : (RemoteAddress) => Unit) : Unit = clusterActor.foreach(_.foreach(f))
|
||||
|
||||
def start : Unit = synchronized {
|
||||
if(supervisor.isEmpty) {
|
||||
for(actor <- createClusterActor;
|
||||
sup <- createSupervisor(actor)) {
|
||||
clusterActor = Some(actor)
|
||||
supervisor = Some(sup)
|
||||
sup.start
|
||||
def foreach(f: (RemoteAddress) => Unit): Unit = clusterActor.foreach(_.foreach(f))
|
||||
|
||||
def start: Unit = start(None)
|
||||
|
||||
def start(serializerClassLoader : Option[ClassLoader]): Unit = synchronized {
|
||||
log.info("Starting up Cluster Service...")
|
||||
if (clusterActor.isEmpty) {
|
||||
for{ actor <- createClusterActor(serializerClassLoader getOrElse getClass.getClassLoader)
|
||||
sup <- createSupervisor(actor) } {
|
||||
clusterActor = Some(actor)
|
||||
sup.start
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def shutdown : Unit = synchronized {
|
||||
supervisor.foreach(_.stop)
|
||||
supervisor = None
|
||||
def shutdown: Unit = synchronized {
|
||||
log.info("Shutting down Cluster Service...")
|
||||
for{
|
||||
c <- clusterActor
|
||||
s <- c._supervisor
|
||||
} s.stop
|
||||
clusterActor = None
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import se.scalablesolutions.akka.remote.protobuf.RemoteProtocol.{RemoteRequest,
|
|||
import se.scalablesolutions.akka.actor.{Exit, Actor}
|
||||
import se.scalablesolutions.akka.dispatch.{DefaultCompletableFuture, CompletableFuture}
|
||||
import se.scalablesolutions.akka.util.{UUID, Logging}
|
||||
import se.scalablesolutions.akka.Config.config
|
||||
import se.scalablesolutions.akka.config.Config.config
|
||||
|
||||
import org.jboss.netty.channel._
|
||||
import group.DefaultChannelGroup
|
||||
|
|
|
|||
|
|
@ -18,19 +18,17 @@ object RemoteProtocolBuilder {
|
|||
private var SERIALIZER_PROTOBUF: Serializer.Protobuf = Serializer.Protobuf
|
||||
|
||||
|
||||
def setClassLoader(classLoader: ClassLoader) = {
|
||||
SERIALIZER_JAVA = new Serializer.Java
|
||||
SERIALIZER_JAVA_JSON = new Serializer.JavaJSON
|
||||
SERIALIZER_SCALA_JSON = new Serializer.ScalaJSON
|
||||
SERIALIZER_JAVA.setClassLoader(classLoader)
|
||||
SERIALIZER_JAVA_JSON.setClassLoader(classLoader)
|
||||
SERIALIZER_SCALA_JSON.setClassLoader(classLoader)
|
||||
def setClassLoader(cl: ClassLoader) = {
|
||||
SERIALIZER_JAVA.classLoader = Some(cl)
|
||||
SERIALIZER_JAVA_JSON.classLoader = Some(cl)
|
||||
SERIALIZER_SCALA_JSON.classLoader = Some(cl)
|
||||
}
|
||||
|
||||
def getMessage(request: RemoteRequest): Any = {
|
||||
request.getProtocol match {
|
||||
case SerializationProtocol.SBINARY =>
|
||||
val renderer = Class.forName(new String(request.getMessageManifest.toByteArray)).newInstance.asInstanceOf[SBinary[_ <: AnyRef]]
|
||||
val renderer = Class.forName(
|
||||
new String(request.getMessageManifest.toByteArray)).newInstance.asInstanceOf[SBinary[_ <: AnyRef]]
|
||||
renderer.fromBytes(request.getMessage.toByteArray)
|
||||
case SerializationProtocol.SCALA_JSON =>
|
||||
val manifest = SERIALIZER_JAVA.in(request.getMessageManifest.toByteArray, None).asInstanceOf[String]
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import java.util.{Map => JMap}
|
|||
import se.scalablesolutions.akka.actor._
|
||||
import se.scalablesolutions.akka.util._
|
||||
import se.scalablesolutions.akka.remote.protobuf.RemoteProtocol.{RemoteReply, RemoteRequest}
|
||||
import se.scalablesolutions.akka.Config.config
|
||||
import se.scalablesolutions.akka.config.Config.config
|
||||
|
||||
import org.jboss.netty.bootstrap.ServerBootstrap
|
||||
import org.jboss.netty.channel._
|
||||
|
|
@ -58,7 +58,7 @@ object RemoteNode extends RemoteServer
|
|||
*/
|
||||
object RemoteServer {
|
||||
val HOSTNAME = config.getString("akka.remote.server.hostname", "localhost")
|
||||
val PORT = config.getInt("akka.remote.server.port", 9966)
|
||||
val PORT = config.getInt("akka.remote.server.port", 9999)
|
||||
|
||||
val CONNECTION_TIMEOUT_MILLIS = config.getInt("akka.remote.server.connection-timeout", 1000)
|
||||
|
||||
|
|
|
|||
|
|
@ -5,10 +5,15 @@
|
|||
package se.scalablesolutions.akka.serialization
|
||||
|
||||
import org.codehaus.jackson.map.ObjectMapper
|
||||
|
||||
import com.google.protobuf.Message
|
||||
import reflect.Manifest
|
||||
|
||||
import scala.reflect.Manifest
|
||||
|
||||
import sbinary.DefaultProtocol
|
||||
|
||||
import java.io.{StringWriter, ByteArrayOutputStream, ObjectOutputStream}
|
||||
|
||||
import sjson.json.{Serializer=>SJSONSerializer}
|
||||
|
||||
object SerializationProtocol {
|
||||
|
|
|
|||
|
|
@ -18,8 +18,12 @@ import sjson.json.{Serializer => SJSONSerializer}
|
|||
* @author <a href="http://jonasboner.com">Jonas Bonér</a>
|
||||
*/
|
||||
trait Serializer {
|
||||
var classLoader: Option[ClassLoader] = None
|
||||
|
||||
def deepClone(obj: AnyRef): AnyRef
|
||||
|
||||
def out(obj: AnyRef): Array[Byte]
|
||||
|
||||
def in(bytes: Array[Byte], clazz: Option[Class[_]]): AnyRef
|
||||
}
|
||||
|
||||
|
|
@ -51,11 +55,7 @@ object Serializer {
|
|||
* @author <a href="http://jonasboner.com">Jonas Bonér</a>
|
||||
*/
|
||||
object Java extends Java
|
||||
class Java extends Serializer {
|
||||
private var classLoader: Option[ClassLoader] = None
|
||||
|
||||
def setClassLoader(cl: ClassLoader) = classLoader = Some(cl)
|
||||
|
||||
trait Java extends Serializer {
|
||||
def deepClone(obj: AnyRef): AnyRef = in(out(obj), None)
|
||||
|
||||
def out(obj: AnyRef): Array[Byte] = {
|
||||
|
|
@ -67,8 +67,9 @@ object Serializer {
|
|||
}
|
||||
|
||||
def in(bytes: Array[Byte], clazz: Option[Class[_]]): AnyRef = {
|
||||
val in = if (classLoader.isDefined) new ClassLoaderObjectInputStream(classLoader.get, new ByteArrayInputStream(bytes))
|
||||
else new ObjectInputStream(new ByteArrayInputStream(bytes))
|
||||
val in =
|
||||
if (classLoader.isDefined) new ClassLoaderObjectInputStream(classLoader.get, new ByteArrayInputStream(bytes))
|
||||
else new ObjectInputStream(new ByteArrayInputStream(bytes))
|
||||
val obj = in.readObject
|
||||
in.close
|
||||
obj
|
||||
|
|
@ -79,18 +80,21 @@ object Serializer {
|
|||
* @author <a href="http://jonasboner.com">Jonas Bonér</a>
|
||||
*/
|
||||
object Protobuf extends Protobuf
|
||||
class Protobuf extends Serializer {
|
||||
trait Protobuf extends Serializer {
|
||||
def deepClone(obj: AnyRef): AnyRef = in(out(obj), Some(obj.getClass))
|
||||
|
||||
def out(obj: AnyRef): Array[Byte] = {
|
||||
if (!obj.isInstanceOf[Message]) throw new IllegalArgumentException("Can't serialize a non-protobuf message using protobuf [" + obj + "]")
|
||||
if (!obj.isInstanceOf[Message]) throw new IllegalArgumentException(
|
||||
"Can't serialize a non-protobuf message using protobuf [" + obj + "]")
|
||||
obj.asInstanceOf[Message].toByteArray
|
||||
}
|
||||
|
||||
def in(bytes: Array[Byte], clazz: Option[Class[_]]): AnyRef = {
|
||||
if (!clazz.isDefined) throw new IllegalArgumentException("Need a protobuf message class to be able to serialize bytes using protobuf")
|
||||
if (!clazz.isDefined) throw new IllegalArgumentException(
|
||||
"Need a protobuf message class to be able to serialize bytes using protobuf")
|
||||
// TODO: should we cache this method lookup?
|
||||
val message = clazz.get.getDeclaredMethod("getDefaultInstance", EMPTY_CLASS_ARRAY: _*).invoke(null, EMPTY_ANY_REF_ARRAY: _*).asInstanceOf[Message]
|
||||
val message = clazz.get.getDeclaredMethod(
|
||||
"getDefaultInstance", EMPTY_CLASS_ARRAY: _*).invoke(null, EMPTY_ANY_REF_ARRAY: _*).asInstanceOf[Message]
|
||||
message.toBuilder().mergeFrom(bytes).build
|
||||
}
|
||||
|
||||
|
|
@ -104,13 +108,9 @@ object Serializer {
|
|||
* @author <a href="http://jonasboner.com">Jonas Bonér</a>
|
||||
*/
|
||||
object JavaJSON extends JavaJSON
|
||||
class JavaJSON extends Serializer {
|
||||
trait JavaJSON extends Serializer {
|
||||
private val mapper = new ObjectMapper
|
||||
|
||||
private var classLoader: Option[ClassLoader] = None
|
||||
|
||||
def setClassLoader(cl: ClassLoader) = classLoader = Some(cl)
|
||||
|
||||
def deepClone(obj: AnyRef): AnyRef = in(out(obj), Some(obj.getClass))
|
||||
|
||||
def out(obj: AnyRef): Array[Byte] = {
|
||||
|
|
@ -122,9 +122,11 @@ object Serializer {
|
|||
}
|
||||
|
||||
def in(bytes: Array[Byte], clazz: Option[Class[_]]): AnyRef = {
|
||||
if (!clazz.isDefined) throw new IllegalArgumentException("Can't deserialize JSON to instance if no class is provided")
|
||||
val in = if (classLoader.isDefined) new ClassLoaderObjectInputStream(classLoader.get, new ByteArrayInputStream(bytes))
|
||||
else new ObjectInputStream(new ByteArrayInputStream(bytes))
|
||||
if (!clazz.isDefined) throw new IllegalArgumentException(
|
||||
"Can't deserialize JSON to instance if no class is provided")
|
||||
val in =
|
||||
if (classLoader.isDefined) new ClassLoaderObjectInputStream(classLoader.get, new ByteArrayInputStream(bytes))
|
||||
else new ObjectInputStream(new ByteArrayInputStream(bytes))
|
||||
val obj = mapper.readValue(in, clazz.get).asInstanceOf[AnyRef]
|
||||
in.close
|
||||
obj
|
||||
|
|
@ -140,13 +142,9 @@ object Serializer {
|
|||
* @author <a href="http://jonasboner.com">Jonas Bonér</a>
|
||||
*/
|
||||
object ScalaJSON extends ScalaJSON
|
||||
class ScalaJSON extends Serializer {
|
||||
trait ScalaJSON extends Serializer {
|
||||
def deepClone(obj: AnyRef): AnyRef = in(out(obj), None)
|
||||
|
||||
private var classLoader: Option[ClassLoader] = None
|
||||
|
||||
def setClassLoader(cl: ClassLoader) = classLoader = Some(cl)
|
||||
|
||||
def out(obj: AnyRef): Array[Byte] = SJSONSerializer.SJSON.out(obj)
|
||||
|
||||
// FIXME set ClassLoader on SJSONSerializer.SJSON
|
||||
|
|
@ -166,7 +164,7 @@ object Serializer {
|
|||
* @author <a href="http://jonasboner.com">Jonas Bonér</a>
|
||||
*/
|
||||
object SBinary extends SBinary
|
||||
class SBinary {
|
||||
trait SBinary {
|
||||
import sbinary.DefaultProtocol._
|
||||
|
||||
def deepClone[T <: AnyRef](obj: T)(implicit w : Writes[T], r : Reads[T]): T = in[T](out[T](obj), None)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
||||
*/
|
||||
|
||||
package se.scalablesolutions.akka.state
|
||||
package se.scalablesolutions.akka.stm
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import java.util.concurrent.{ConcurrentLinkedQueue, LinkedBlockingQueue}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@
|
|||
POSSIBILITY OF SUCH DAMAGE.
|
||||
**/
|
||||
|
||||
package se.scalablesolutions.akka.collection
|
||||
package se.scalablesolutions.akka.stm
|
||||
|
||||
trait PersistentDataStructure
|
||||
|
||||
|
|
@ -77,7 +77,7 @@ object HashTrie {
|
|||
// nodes
|
||||
|
||||
@serializable
|
||||
private[collection] sealed trait Node[K, +V] {
|
||||
private[stm] sealed trait Node[K, +V] {
|
||||
val size: Int
|
||||
|
||||
def apply(key: K, hash: Int): Option[V]
|
||||
|
|
@ -90,7 +90,7 @@ private[collection] sealed trait Node[K, +V] {
|
|||
}
|
||||
|
||||
@serializable
|
||||
private[collection] class EmptyNode[K] extends Node[K, Nothing] {
|
||||
private[stm] class EmptyNode[K] extends Node[K, Nothing] {
|
||||
val size = 0
|
||||
|
||||
def apply(key: K, hash: Int) = None
|
||||
|
|
@ -106,12 +106,12 @@ private[collection] class EmptyNode[K] extends Node[K, Nothing] {
|
|||
}
|
||||
}
|
||||
|
||||
private[collection] abstract class SingleNode[K, +V] extends Node[K, V] {
|
||||
private[stm] abstract class SingleNode[K, +V] extends Node[K, V] {
|
||||
val hash: Int
|
||||
}
|
||||
|
||||
|
||||
private[collection] class LeafNode[K, +V](key: K, val hash: Int, value: V) extends SingleNode[K, V] {
|
||||
private[stm] class LeafNode[K, +V](key: K, val hash: Int, value: V) extends SingleNode[K, V] {
|
||||
val size = 1
|
||||
|
||||
def apply(key: K, hash: Int) = if (this.key == key) Some(value) else None
|
||||
|
|
@ -141,7 +141,7 @@ private[collection] class LeafNode[K, +V](key: K, val hash: Int, value: V) exten
|
|||
}
|
||||
|
||||
|
||||
private[collection] class CollisionNode[K, +V](val hash: Int, bucket: List[(K, V)]) extends SingleNode[K, V] {
|
||||
private[stm] class CollisionNode[K, +V](val hash: Int, bucket: List[(K, V)]) extends SingleNode[K, V] {
|
||||
lazy val size = bucket.length
|
||||
|
||||
def this(hash: Int, pairs: (K, V)*) = this(hash, pairs.toList)
|
||||
|
|
@ -185,7 +185,7 @@ private[collection] class CollisionNode[K, +V](val hash: Int, bucket: List[(K, V
|
|||
override def toString = "CollisionNode(" + bucket.toString + ")"
|
||||
}
|
||||
|
||||
private[collection] class BitmappedNode[K, +V](shift: Int)(table: Array[Node[K, V]], bits: Int) extends Node[K, V] {
|
||||
private[stm] class BitmappedNode[K, +V](shift: Int)(table: Array[Node[K, V]], bits: Int) extends Node[K, V] {
|
||||
lazy val size = {
|
||||
val sizes = for {
|
||||
n <- table
|
||||
|
|
@ -284,7 +284,7 @@ private[collection] class BitmappedNode[K, +V](shift: Int)(table: Array[Node[K,
|
|||
}
|
||||
|
||||
|
||||
private[collection] object BitmappedNode {
|
||||
private[stm] object BitmappedNode {
|
||||
def apply[K, V](shift: Int)(node: SingleNode[K, V], key: K, hash: Int, value: V) = {
|
||||
val table = new Array[Node[K, V]](Math.max((hash >>> shift) & 0x01f, (node.hash >>> shift) & 0x01f) + 1)
|
||||
|
||||
|
|
@ -312,7 +312,7 @@ private[collection] object BitmappedNode {
|
|||
}
|
||||
|
||||
|
||||
private[collection] class FullNode[K, +V](shift: Int)(table: Array[Node[K, V]]) extends Node[K, V] {
|
||||
private[stm] class FullNode[K, +V](shift: Int)(table: Array[Node[K, V]]) extends Node[K, V] {
|
||||
lazy val size = table.foldLeft(0) { _ + _.size }
|
||||
|
||||
def apply(key: K, hash: Int) = table((hash >>> shift) & 0x01f)(key, hash)
|
||||
|
|
|
|||
|
|
@ -2,9 +2,7 @@
|
|||
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
||||
*/
|
||||
|
||||
package se.scalablesolutions.akka.util
|
||||
|
||||
import stm.Transaction
|
||||
package se.scalablesolutions.akka.stm
|
||||
|
||||
/**
|
||||
* Reference that can hold either a typed value or an exception.
|
||||
|
|
|
|||
|
|
@ -6,16 +6,18 @@ package se.scalablesolutions.akka.stm
|
|||
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
import scala.collection.mutable.HashMap
|
||||
|
||||
import se.scalablesolutions.akka.state.Committable
|
||||
import se.scalablesolutions.akka.util.Logging
|
||||
|
||||
import org.multiverse.api.{Transaction => MultiverseTransaction}
|
||||
import org.multiverse.api.GlobalStmInstance.getGlobalStmInstance
|
||||
import org.multiverse.api.ThreadLocalTransaction._
|
||||
import org.multiverse.templates.OrElseTemplate
|
||||
|
||||
import scala.collection.mutable.HashMap
|
||||
import org.multiverse.templates.{TransactionTemplate, OrElseTemplate}
|
||||
import org.multiverse.utils.backoff.ExponentialBackoffPolicy
|
||||
import org.multiverse.stms.alpha.AlphaStm
|
||||
|
||||
class NoTransactionInScopeException extends RuntimeException
|
||||
class TransactionRetryException(message: String) extends RuntimeException(message)
|
||||
|
|
@ -30,8 +32,8 @@ class TransactionRetryException(message: String) extends RuntimeException(messag
|
|||
* Here are some examples (assuming implicit transaction family name in scope):
|
||||
* <pre>
|
||||
* import se.scalablesolutions.akka.stm.Transaction._
|
||||
*
|
||||
* atomic {
|
||||
*
|
||||
* atomic {
|
||||
* .. // do something within a transaction
|
||||
* }
|
||||
* </pre>
|
||||
|
|
@ -39,8 +41,8 @@ class TransactionRetryException(message: String) extends RuntimeException(messag
|
|||
* Example of atomic transaction management using atomic block with retry count:
|
||||
* <pre>
|
||||
* import se.scalablesolutions.akka.stm.Transaction._
|
||||
*
|
||||
* atomic(maxNrOfRetries) {
|
||||
*
|
||||
* atomic(maxNrOfRetries) {
|
||||
* .. // do something within a transaction
|
||||
* }
|
||||
* </pre>
|
||||
|
|
@ -49,10 +51,10 @@ class TransactionRetryException(message: String) extends RuntimeException(messag
|
|||
* Which is a good way to reduce contention and transaction collisions.
|
||||
* <pre>
|
||||
* import se.scalablesolutions.akka.stm.Transaction._
|
||||
*
|
||||
* atomically {
|
||||
*
|
||||
* atomically {
|
||||
* .. // try to do something
|
||||
* } orElse {
|
||||
* } orElse {
|
||||
* .. // if transaction clashes try do do something else to minimize contention
|
||||
* }
|
||||
* </pre>
|
||||
|
|
@ -61,11 +63,11 @@ class TransactionRetryException(message: String) extends RuntimeException(messag
|
|||
*
|
||||
* <pre>
|
||||
* import se.scalablesolutions.akka.stm.Transaction._
|
||||
* for (tx <- Transaction) {
|
||||
* for (tx <- Transaction) {
|
||||
* ... // do transactional stuff
|
||||
* }
|
||||
*
|
||||
* val result = for (tx <- Transaction) yield {
|
||||
* val result = for (tx <- Transaction) yield {
|
||||
* ... // do transactional stuff yielding a result
|
||||
* }
|
||||
* </pre>
|
||||
|
|
@ -78,17 +80,17 @@ class TransactionRetryException(message: String) extends RuntimeException(messag
|
|||
*
|
||||
* // You can use them together with Transaction in a for comprehension since
|
||||
* // TransactionalRef is also monadic
|
||||
* for {
|
||||
* for {
|
||||
* tx <- Transaction
|
||||
* ref <- refs
|
||||
* } {
|
||||
* ... // use the ref inside a transaction
|
||||
* }
|
||||
*
|
||||
* val result = for {
|
||||
* val result = for {
|
||||
* tx <- Transaction
|
||||
* ref <- refs
|
||||
* } yield {
|
||||
* } yield {
|
||||
* ... // use the ref inside a transaction, yield a result
|
||||
* }
|
||||
* </pre>
|
||||
|
|
@ -97,101 +99,87 @@ class TransactionRetryException(message: String) extends RuntimeException(messag
|
|||
*/
|
||||
object Transaction extends TransactionManagement {
|
||||
val idFactory = new AtomicLong(-1L)
|
||||
/*
|
||||
import AlphaStm._
|
||||
private val defaultTxBuilder = new AlphaTransactionFactoryBuilder
|
||||
defaultTxBuilder.setReadonly(false)
|
||||
defaultTxBuilder.setInterruptible(INTERRUPTIBLE)
|
||||
defaultTxBuilder.setMaxRetryCount(MAX_NR_OF_RETRIES)
|
||||
defaultTxBuilder.setPreventWriteSkew(PREVENT_WRITE_SKEW)
|
||||
defaultTxBuilder.setAutomaticReadTracking(AUTOMATIC_READ_TRACKING)
|
||||
defaultTxBuilder.setSmartTxLengthSelector(SMART_TX_LENGTH_SELECTOR)
|
||||
defaultTxBuilder.setBackoffPolicy(new ExponentialBackoffPolicy)
|
||||
private val readOnlyTxBuilder = new AlphaStm.AlphaTransactionFactoryBuilder
|
||||
readOnlyTxBuilder.setReadonly(true)
|
||||
readOnlyTxBuilder.setInterruptible(INTERRUPTIBLE)
|
||||
readOnlyTxBuilder.setMaxRetryCount(MAX_NR_OF_RETRIES)
|
||||
readOnlyTxBuilder.setPreventWriteSkew(PREVENT_WRITE_SKEW)
|
||||
readOnlyTxBuilder.setAutomaticReadTracking(AUTOMATIC_READ_TRACKING)
|
||||
readOnlyTxBuilder.setSmartTxLengthSelector(SMART_TX_LENGTH_SELECTOR)
|
||||
readOnlyTxBuilder.setBackoffPolicy(new ExponentialBackoffPolicy)
|
||||
*/
|
||||
/**
|
||||
* See ScalaDoc on class.
|
||||
*/
|
||||
def map[T](f: => T)(implicit transactionFamilyName: String): T =
|
||||
atomic {f}
|
||||
|
||||
/**
|
||||
* See ScalaDoc on class.
|
||||
*/
|
||||
def map[T](f: Transaction => T)(implicit transactionFamilyName: String): T = atomic { f(getTransactionInScope) }
|
||||
def flatMap[T](f: => T)(implicit transactionFamilyName: String): T =
|
||||
atomic {f}
|
||||
|
||||
/**
|
||||
* See ScalaDoc on class.
|
||||
*/
|
||||
def flatMap[T](f: Transaction => T)(implicit transactionFamilyName: String): T = atomic { f(getTransactionInScope) }
|
||||
|
||||
/**
|
||||
* See ScalaDoc on class.
|
||||
*/
|
||||
def foreach(f: Transaction => Unit)(implicit transactionFamilyName: String): Unit = atomic { f(getTransactionInScope) }
|
||||
def foreach(f: => Unit)(implicit transactionFamilyName: String): Unit =
|
||||
atomic {f}
|
||||
|
||||
/**
|
||||
* Creates a "pure" STM atomic transaction and by-passes all transactions hooks
|
||||
* such as persistence etc.
|
||||
* Only for internal usage.
|
||||
*/
|
||||
private[akka] def pureAtomic[T](body: => T): T = new AtomicTemplate[T](
|
||||
getGlobalStmInstance, "internal", false, false, TransactionManagement.MAX_NR_OF_RETRIES) {
|
||||
private[akka] def pureAtomic[T](body: => T): T = new TransactionTemplate[T]() {
|
||||
def execute(mtx: MultiverseTransaction): T = body
|
||||
}.execute()
|
||||
|
||||
/**
|
||||
* See ScalaDoc on class.
|
||||
*/
|
||||
def atomic[T](body: => T)(implicit transactionFamilyName: String): T = new AtomicTemplate[T](
|
||||
getGlobalStmInstance, transactionFamilyName, false, false, TransactionManagement.MAX_NR_OF_RETRIES) {
|
||||
def execute(mtx: MultiverseTransaction): T = body
|
||||
override def postStart(mtx: MultiverseTransaction) = {
|
||||
val tx = new Transaction
|
||||
tx.transaction = Some(mtx)
|
||||
setTransaction(Some(tx))
|
||||
}
|
||||
override def postCommit = {
|
||||
if (isTransactionInScope) getTransactionInScope.commit
|
||||
else throw new IllegalStateException("No transaction in scope")
|
||||
}
|
||||
}.execute()
|
||||
def atomic[T](body: => T)(implicit transactionFamilyName: String): T = {
|
||||
// defaultTxBuilder.setFamilyName(transactionFamilyName)
|
||||
// new TransactionTemplate[T](defaultTxBuilder.build) {
|
||||
new TransactionTemplate[T]() { // FIXME take factory
|
||||
def execute(mtx: MultiverseTransaction): T = {
|
||||
val result = body
|
||||
|
||||
/**
|
||||
* See ScalaDoc on class.
|
||||
*/
|
||||
def atomic[T](retryCount: Int)(body: => T)(implicit transactionFamilyName: String): T = {
|
||||
new AtomicTemplate[T](getGlobalStmInstance, transactionFamilyName, false, false, retryCount) {
|
||||
def execute(mtx: MultiverseTransaction): T = body
|
||||
override def postStart(mtx: MultiverseTransaction) = {
|
||||
log.trace("Committing transaction [%s] \nwith family name [%s] \nby joining transaction set")
|
||||
getTransactionSetInScope.joinCommit(mtx)
|
||||
|
||||
// FIXME tryJoinCommit(mtx, TransactionManagement.TRANSACTION_TIMEOUT, TimeUnit.MILLISECONDS)
|
||||
//getTransactionSetInScope.tryJoinCommit(mtx, TransactionManagement.TRANSACTION_TIMEOUT, TimeUnit.MILLISECONDS)
|
||||
|
||||
clearTransaction
|
||||
result
|
||||
}
|
||||
|
||||
override def onStart(mtx: MultiverseTransaction) = {
|
||||
val txSet = if (!isTransactionSetInScope) createNewTransactionSet
|
||||
else getTransactionSetInScope
|
||||
val tx = new Transaction
|
||||
tx.transaction = Some(mtx)
|
||||
setTransaction(Some(tx))
|
||||
}
|
||||
override def postCommit = {
|
||||
if (isTransactionInScope) getTransactionInScope.commit
|
||||
else throw new IllegalStateException("No transaction in scope")
|
||||
}
|
||||
}.execute
|
||||
}
|
||||
|
||||
/**
|
||||
* See ScalaDoc on class.
|
||||
*/
|
||||
def atomicReadOnly[T](retryCount: Int)(body: => T)(implicit transactionFamilyName: String): T = {
|
||||
new AtomicTemplate[T](getGlobalStmInstance, transactionFamilyName, false, true, retryCount) {
|
||||
def execute(mtx: MultiverseTransaction): T = body
|
||||
override def postStart(mtx: MultiverseTransaction) = {
|
||||
val tx = new Transaction
|
||||
tx.transaction = Some(mtx)
|
||||
setTransaction(Some(tx))
|
||||
txSet.registerOnCommitTask(new Runnable() {
|
||||
def run = tx.commit
|
||||
})
|
||||
txSet.registerOnAbortTask(new Runnable() {
|
||||
def run = tx.abort
|
||||
})
|
||||
}
|
||||
override def postCommit = {
|
||||
if (isTransactionInScope) getTransactionInScope.commit
|
||||
else throw new IllegalStateException("No transaction in scope")
|
||||
}
|
||||
}.execute
|
||||
}
|
||||
|
||||
/**
|
||||
* See ScalaDoc on class.
|
||||
*/
|
||||
def atomicReadOnly[T](body: => T): T = {
|
||||
new AtomicTemplate[T](true) {
|
||||
def execute(mtx: MultiverseTransaction): T = body
|
||||
override def postStart(mtx: MultiverseTransaction) = {
|
||||
val tx = new Transaction
|
||||
tx.transaction = Some(mtx)
|
||||
setTransaction(Some(tx))
|
||||
}
|
||||
override def postCommit = {
|
||||
if (isTransactionInScope) getTransactionInScope.commit
|
||||
else throw new IllegalStateException("No transaction in scope")
|
||||
}
|
||||
}.execute
|
||||
}.execute()
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -216,23 +204,28 @@ object Transaction extends TransactionManagement {
|
|||
*/
|
||||
@serializable class Transaction extends Logging {
|
||||
import Transaction._
|
||||
|
||||
|
||||
log.trace("Creating %s", toString)
|
||||
val id = Transaction.idFactory.incrementAndGet
|
||||
@volatile private[this] var status: TransactionStatus = TransactionStatus.New
|
||||
private[akka] var transaction: Option[MultiverseTransaction] = None
|
||||
private[this] val persistentStateMap = new HashMap[String, Committable]
|
||||
private[akka] val depth = new AtomicInteger(0)
|
||||
|
||||
|
||||
// --- public methods ---------
|
||||
|
||||
def commit = synchronized {
|
||||
log.trace("Committing transaction %s", toString)
|
||||
pureAtomic {
|
||||
persistentStateMap.values.foreach(_.commit)
|
||||
TransactionManagement.clearTransaction
|
||||
}
|
||||
status = TransactionStatus.Completed
|
||||
}
|
||||
|
||||
def abort = synchronized {
|
||||
log.trace("Aborting transaction %s", toString)
|
||||
}
|
||||
|
||||
def isNew = synchronized { status == TransactionStatus.New }
|
||||
|
||||
def isActive = synchronized { status == TransactionStatus.Active }
|
||||
|
|
@ -259,13 +252,13 @@ object Transaction extends TransactionManagement {
|
|||
|
||||
private def ensureIsActiveOrAborted =
|
||||
if (!(status == TransactionStatus.Active || status == TransactionStatus.Aborted))
|
||||
throw new IllegalStateException(
|
||||
"Expected ACTIVE or ABORTED transaction - current status [" + status + "]: " + toString)
|
||||
throw new IllegalStateException(
|
||||
"Expected ACTIVE or ABORTED transaction - current status [" + status + "]: " + toString)
|
||||
|
||||
private def ensureIsActiveOrNew =
|
||||
if (!(status == TransactionStatus.Active || status == TransactionStatus.New))
|
||||
throw new IllegalStateException(
|
||||
"Expected ACTIVE or NEW transaction - current status [" + status + "]: " + toString)
|
||||
throw new IllegalStateException(
|
||||
"Expected ACTIVE or NEW transaction - current status [" + status + "]: " + toString)
|
||||
|
||||
// For reinitialize transaction after sending it over the wire
|
||||
private[akka] def reinit = synchronized {
|
||||
|
|
@ -277,14 +270,14 @@ object Transaction extends TransactionManagement {
|
|||
}
|
||||
|
||||
override def equals(that: Any): Boolean = synchronized {
|
||||
that != null &&
|
||||
that.isInstanceOf[Transaction] &&
|
||||
that.asInstanceOf[Transaction].id == this.id
|
||||
that != null &&
|
||||
that.isInstanceOf[Transaction] &&
|
||||
that.asInstanceOf[Transaction].id == this.id
|
||||
}
|
||||
|
||||
|
||||
override def hashCode(): Int = synchronized { id.toInt }
|
||||
|
||||
override def toString(): String = synchronized { "Transaction[" + id + ", " + status + "]" }
|
||||
|
||||
override def toString = synchronized { "Transaction[" + id + ", " + status + "]" }
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -9,51 +9,80 @@ import java.util.concurrent.atomic.AtomicBoolean
|
|||
import se.scalablesolutions.akka.util.Logging
|
||||
|
||||
import org.multiverse.api.ThreadLocalTransaction._
|
||||
import org.multiverse.commitbarriers.CountDownCommitBarrier
|
||||
|
||||
class StmException(msg: String) extends RuntimeException(msg)
|
||||
|
||||
class TransactionAwareWrapperException(
|
||||
val cause: Throwable, val tx: Option[Transaction]) extends RuntimeException(cause) {
|
||||
override def toString(): String = "TransactionAwareWrapperException[" + cause + ", " + tx + "]"
|
||||
class TransactionAwareWrapperException(val cause: Throwable, val tx: Option[Transaction]) extends RuntimeException(cause) {
|
||||
override def toString = "TransactionAwareWrapperException[" + cause + ", " + tx + "]"
|
||||
}
|
||||
|
||||
object TransactionManagement extends TransactionManagement {
|
||||
import se.scalablesolutions.akka.Config._
|
||||
|
||||
val MAX_NR_OF_RETRIES = config.getInt("akka.stm.max-nr-of-retries", 100)
|
||||
val TRANSACTION_ENABLED = new AtomicBoolean(config.getBool("akka.stm.service", false))
|
||||
import se.scalablesolutions.akka.config.Config._
|
||||
|
||||
val TRANSACTION_ENABLED = new AtomicBoolean(config.getBool("akka.stm.service", false))
|
||||
val FAIR_TRANSACTIONS = config.getBool("akka.stm.fair", true)
|
||||
val INTERRUPTIBLE = config.getBool("akka.stm.interruptible", true)
|
||||
val MAX_NR_OF_RETRIES = config.getInt("akka.stm.max-nr-of-retries", 1000)
|
||||
val TRANSACTION_TIMEOUT = config.getInt("akka.stm.timeout", 10000)
|
||||
val SMART_TX_LENGTH_SELECTOR = config.getBool("akka.stm.smart-tx-length-selector", true)
|
||||
def isTransactionalityEnabled = TRANSACTION_ENABLED.get
|
||||
|
||||
def disableTransactions = TRANSACTION_ENABLED.set(false)
|
||||
|
||||
private[akka] val currentTransaction: ThreadLocal[Option[Transaction]] = new ThreadLocal[Option[Transaction]]() {
|
||||
private[akka] val transactionSet = new ThreadLocal[Option[CountDownCommitBarrier]]() {
|
||||
override protected def initialValue: Option[CountDownCommitBarrier] = None
|
||||
}
|
||||
|
||||
private[akka] val transaction = new ThreadLocal[Option[Transaction]]() {
|
||||
override protected def initialValue: Option[Transaction] = None
|
||||
}
|
||||
|
||||
private[akka] def getTransactionSet: CountDownCommitBarrier = {
|
||||
val option = transactionSet.get
|
||||
if ((option eq null) || option.isEmpty) throw new IllegalStateException("No TransactionSet in scope")
|
||||
option.get
|
||||
}
|
||||
|
||||
private[akka] def getTransaction: Transaction = {
|
||||
val option = transaction.get
|
||||
if ((option eq null) || option.isEmpty) throw new IllegalStateException("No Transaction in scope")
|
||||
option.get
|
||||
}
|
||||
}
|
||||
|
||||
trait TransactionManagement extends Logging {
|
||||
import TransactionManagement.currentTransaction
|
||||
|
||||
private[akka] def createNewTransaction = currentTransaction.set(Some(new Transaction))
|
||||
|
||||
private[akka] def setTransaction(transaction: Option[Transaction]) = if (transaction.isDefined) {
|
||||
val tx = transaction.get
|
||||
currentTransaction.set(transaction)
|
||||
if (tx.transaction.isDefined) setThreadLocalTransaction(tx.transaction.get)
|
||||
else throw new IllegalStateException("No transaction defined")
|
||||
private[akka] def createNewTransactionSet: CountDownCommitBarrier = {
|
||||
val txSet = new CountDownCommitBarrier(1, TransactionManagement.FAIR_TRANSACTIONS)
|
||||
TransactionManagement.transactionSet.set(Some(txSet))
|
||||
txSet
|
||||
}
|
||||
|
||||
private[akka] def setTransactionSet(txSet: Option[CountDownCommitBarrier]) =
|
||||
if (txSet.isDefined) TransactionManagement.transactionSet.set(txSet)
|
||||
|
||||
private[akka] def setTransaction(tx: Option[Transaction]) =
|
||||
if (tx.isDefined) TransactionManagement.transaction.set(tx)
|
||||
|
||||
private[akka] def clearTransactionSet = TransactionManagement.transactionSet.set(None)
|
||||
|
||||
private[akka] def clearTransaction = {
|
||||
currentTransaction.set(None)
|
||||
TransactionManagement.transaction.set(None)
|
||||
setThreadLocalTransaction(null)
|
||||
}
|
||||
|
||||
private[akka] def getTransactionInScope = currentTransaction.get.get
|
||||
|
||||
private[akka] def isTransactionInScope = currentTransaction.get.isDefined
|
||||
private[akka] def getTransactionSetInScope = TransactionManagement.getTransactionSet
|
||||
|
||||
private[akka] def incrementTransaction = if (isTransactionInScope) getTransactionInScope.increment
|
||||
private[akka] def getTransactionInScope = TransactionManagement.getTransaction
|
||||
|
||||
private[akka] def decrementTransaction = if (isTransactionInScope) getTransactionInScope.decrement
|
||||
}
|
||||
private[akka] def isTransactionSetInScope = {
|
||||
val option = TransactionManagement.transactionSet.get
|
||||
(option ne null) && option.isDefined
|
||||
}
|
||||
|
||||
private[akka] def isTransactionInScope = {
|
||||
val option = TransactionManagement.transaction.get
|
||||
(option ne null) && option.isDefined
|
||||
}
|
||||
}
|
||||
|
|
@ -2,14 +2,12 @@
|
|||
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
||||
*/
|
||||
|
||||
package se.scalablesolutions.akka.state
|
||||
package se.scalablesolutions.akka.stm
|
||||
|
||||
import se.scalablesolutions.akka.stm.Transaction.atomic
|
||||
import se.scalablesolutions.akka.stm.NoTransactionInScopeException
|
||||
import se.scalablesolutions.akka.collection._
|
||||
import se.scalablesolutions.akka.util.UUID
|
||||
|
||||
import org.multiverse.datastructures.refs.manual.Ref;
|
||||
import org.multiverse.stms.alpha.AlphaRef
|
||||
|
||||
/**
|
||||
* Example Scala usage:
|
||||
|
|
@ -55,6 +53,17 @@ trait Committable {
|
|||
}
|
||||
|
||||
/**
|
||||
* Alias to TransactionalRef.
|
||||
*
|
||||
* @author <a href="http://jonasboner.com">Jonas Bonér</a>
|
||||
*/
|
||||
object Ref {
|
||||
def apply[T]() = new Ref[T]
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias to Ref.
|
||||
*
|
||||
* @author <a href="http://jonasboner.com">Jonas Bonér</a>
|
||||
*/
|
||||
object TransactionalRef {
|
||||
|
|
@ -67,8 +76,17 @@ object TransactionalRef {
|
|||
def apply[T]() = new TransactionalRef[T]
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a transactional managed reference.
|
||||
* Alias to TransactionalRef.
|
||||
*
|
||||
* @author <a href="http://jonasboner.com">Jonas Bonér</a>
|
||||
*/
|
||||
class Ref[T] extends TransactionalRef[T]
|
||||
|
||||
/**
|
||||
* Implements a transactional managed reference.
|
||||
* Alias to Ref.
|
||||
*
|
||||
* @author <a href="http://jonasboner.com">Jonas Bonér</a>
|
||||
*/
|
||||
|
|
@ -78,7 +96,7 @@ class TransactionalRef[T] extends Transactional {
|
|||
implicit val txInitName = "TransactionalRef:Init"
|
||||
val uuid = UUID.newUuid.toString
|
||||
|
||||
private[this] val ref: Ref[T] = atomic { new Ref }
|
||||
private[this] lazy val ref: AlphaRef[T] = new AlphaRef
|
||||
|
||||
def swap(elem: T) = {
|
||||
ensureIsInTransaction
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@
|
|||
POSSIBILITY OF SUCH DAMAGE.
|
||||
**/
|
||||
|
||||
package se.scalablesolutions.akka.collection
|
||||
package se.scalablesolutions.akka.stm
|
||||
|
||||
import Vector._
|
||||
|
||||
|
|
@ -54,7 +54,7 @@ class Vector[+T] private (val length: Int, shift: Int, root: Array[AnyRef], tail
|
|||
* (somewhat dynamically-typed) implementation in place.
|
||||
*/
|
||||
|
||||
private[collection] def this() = this(0, 5, EmptyArray, EmptyArray)
|
||||
private[stm] def this() = this(0, 5, EmptyArray, EmptyArray)
|
||||
|
||||
def apply(i: Int): T = {
|
||||
if (i >= 0 && i < length) {
|
||||
|
|
@ -317,14 +317,14 @@ class Vector[+T] private (val length: Int, shift: Int, root: Array[AnyRef], tail
|
|||
}
|
||||
|
||||
object Vector {
|
||||
private[collection] val EmptyArray = new Array[AnyRef](0)
|
||||
private[stm] val EmptyArray = new Array[AnyRef](0)
|
||||
|
||||
def apply[T](elems: T*) = elems.foldLeft(EmptyVector:Vector[T]) { _ + _ }
|
||||
|
||||
def unapplySeq[T](vec: Vector[T]): Option[Seq[T]] = Some(vec)
|
||||
|
||||
@inline
|
||||
private[collection] def array(elems: AnyRef*) = {
|
||||
private[stm] def array(elems: AnyRef*) = {
|
||||
val back = new Array[AnyRef](elems.length)
|
||||
Array.copy(elems, 0, back, 0, back.length)
|
||||
|
||||
|
|
@ -334,7 +334,7 @@ object Vector {
|
|||
|
||||
object EmptyVector extends Vector[Nothing]
|
||||
|
||||
private[collection] abstract class VectorProjection[+T] extends Vector[T] {
|
||||
private[stm] abstract class VectorProjection[+T] extends Vector[T] {
|
||||
override val length: Int
|
||||
override def apply(i: Int): T
|
||||
|
||||
|
|
|
|||
|
|
@ -7,18 +7,23 @@ import org.scalatest.junit.JUnitRunner
|
|||
import org.scalatest.matchers.MustMatchers
|
||||
import org.junit.{Test}
|
||||
|
||||
/*
|
||||
@RunWith(classOf[JUnitRunner])
|
||||
class AgentTest extends junit.framework.TestCase with Suite with MustMatchers with ActorTestUtil with Logging {
|
||||
@Test def testAgent = verify(new TestActor {
|
||||
def test = {
|
||||
val t = Agent(5)
|
||||
handle(t){
|
||||
t.update( _ + 1 )
|
||||
t.update( _ * 2 )
|
||||
|
||||
val r = t()
|
||||
r must be (12)
|
||||
}
|
||||
}
|
||||
@Test def testAgent = verify(new TestActor {
|
||||
def test = {
|
||||
atomic {
|
||||
val t = Agent(5)
|
||||
handle(t) {
|
||||
t.update(_ + 1)
|
||||
t.update(_ * 2)
|
||||
|
||||
val r = t()
|
||||
r must be(12)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
*/
|
||||
|
|
@ -49,7 +49,7 @@ class RemoteActorSpecActorAsyncSender extends Actor {
|
|||
class ClientInitiatedRemoteActorTest extends JUnitSuite {
|
||||
import Actor.Sender.Self
|
||||
|
||||
akka.Config.config
|
||||
akka.config.Config.config
|
||||
|
||||
val HOSTNAME = "localhost"
|
||||
val PORT1 = 9990
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
package se.scalablesolutions.akka.actor
|
||||
|
||||
import org.scalatest.junit.JUnitSuite
|
||||
import org.junit.Test
|
||||
import se.scalablesolutions.akka.dispatch.Dispatchers
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
/**
|
||||
* Tests the behaviour of the executor based event driven dispatcher when multiple actors are being dispatched on it.
|
||||
*
|
||||
* @author Jan Van Besien
|
||||
*/
|
||||
class ExecutorBasedEventDrivenDispatcherActorsTest extends JUnitSuite with MustMatchers with ActorTestUtil {
|
||||
class SlowActor(finishedCounter: CountDownLatch) extends Actor {
|
||||
messageDispatcher = Dispatchers.globalExecutorBasedEventDrivenDispatcher
|
||||
id = "SlowActor"
|
||||
|
||||
def receive = {
|
||||
case x: Int => {
|
||||
Thread.sleep(50) // slow actor
|
||||
finishedCounter.countDown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FastActor(finishedCounter: CountDownLatch) extends Actor {
|
||||
messageDispatcher = Dispatchers.globalExecutorBasedEventDrivenDispatcher
|
||||
id = "FastActor"
|
||||
|
||||
def receive = {
|
||||
case x: Int => {
|
||||
finishedCounter.countDown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test def slowActorShouldntBlockFastActor = verify(new TestActor {
|
||||
def test = {
|
||||
val sFinished = new CountDownLatch(50)
|
||||
val fFinished = new CountDownLatch(10)
|
||||
val s = new SlowActor(sFinished)
|
||||
val f = new FastActor(fFinished)
|
||||
|
||||
handle(s, f) {
|
||||
// send a lot of stuff to s
|
||||
for (i <- 1 to 50) {
|
||||
s ! i
|
||||
}
|
||||
|
||||
// send some messages to f
|
||||
for (i <- 1 to 10) {
|
||||
f ! i
|
||||
}
|
||||
|
||||
// now assert that f is finished while s is still busy
|
||||
fFinished.await
|
||||
assert(sFinished.getCount > 0)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
trait ActorTestUtil {
|
||||
def handle[T](actors: Actor*)(test: => T): T = {
|
||||
for (a <- actors) a.start
|
||||
try {
|
||||
test
|
||||
}
|
||||
finally {
|
||||
for (a <- actors) a.stop
|
||||
}
|
||||
}
|
||||
|
||||
def verify(actor: TestActor): Unit = handle(actor) {
|
||||
actor.test
|
||||
}
|
||||
}
|
||||
|
||||
abstract class TestActor extends Actor with ActorTestUtil {
|
||||
def test: Unit
|
||||
|
||||
def receive = {case _ =>}
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ package se.scalablesolutions.akka.actor
|
|||
import org.scalatest.junit.JUnitSuite
|
||||
import org.junit.Test
|
||||
|
||||
import se.scalablesolutions.akka.state.{TransactionalState, TransactionalMap, TransactionalRef, TransactionalVector}
|
||||
import se.scalablesolutions.akka.stm.{TransactionalState, TransactionalMap, TransactionalRef, TransactionalVector}
|
||||
|
||||
case class GetMapState(key: String)
|
||||
case object GetVectorState
|
||||
|
|
@ -23,7 +23,7 @@ case class SuccessOneWay(key: String, value: String)
|
|||
case class FailureOneWay(key: String, value: String, failer: Actor)
|
||||
|
||||
class InMemStatefulActor extends Actor {
|
||||
timeout = 100000
|
||||
timeout = 5000
|
||||
makeTransactionRequired
|
||||
|
||||
private lazy val mapState = TransactionalState.newMap[String, String]
|
||||
|
|
@ -86,8 +86,8 @@ class InMemFailerActor extends Actor {
|
|||
}
|
||||
|
||||
class InMemoryActorTest extends JUnitSuite {
|
||||
import Actor.Sender.Self
|
||||
|
||||
/*
|
||||
@Test
|
||||
def shouldOneWayMapShouldNotRollbackStateForStatefulServerInCaseOfSuccess = {
|
||||
val stateful = new InMemStatefulActor
|
||||
|
|
@ -98,7 +98,7 @@ class InMemoryActorTest extends JUnitSuite {
|
|||
Thread.sleep(1000)
|
||||
assert("new state" === (stateful !! GetMapState("testShouldNotRollbackStateForStatefulServerInCaseOfSuccess")).get)
|
||||
}
|
||||
*/
|
||||
|
||||
@Test
|
||||
def shouldMapShouldNotRollbackStateForStatefulServerInCaseOfSuccess = {
|
||||
val stateful = new InMemStatefulActor
|
||||
|
|
@ -107,7 +107,7 @@ class InMemoryActorTest extends JUnitSuite {
|
|||
stateful !! Success("testShouldNotRollbackStateForStatefulServerInCaseOfSuccess", "new state") // transactionrequired
|
||||
assert("new state" === (stateful !! GetMapState("testShouldNotRollbackStateForStatefulServerInCaseOfSuccess")).get)
|
||||
}
|
||||
/*
|
||||
|
||||
@Test
|
||||
def shouldOneWayMapShouldRollbackStateForStatefulServerInCaseOfFailure = {
|
||||
val stateful = new InMemStatefulActor
|
||||
|
|
@ -120,7 +120,7 @@ class InMemoryActorTest extends JUnitSuite {
|
|||
Thread.sleep(1000)
|
||||
assert("init" === (stateful !! GetMapState("testShouldRollbackStateForStatefulServerInCaseOfFailure")).get) // check that state is == init state
|
||||
}
|
||||
*/
|
||||
|
||||
@Test
|
||||
def shouldMapShouldRollbackStateForStatefulServerInCaseOfFailure = {
|
||||
val stateful = new InMemStatefulActor
|
||||
|
|
@ -134,7 +134,7 @@ class InMemoryActorTest extends JUnitSuite {
|
|||
} catch {case e: RuntimeException => {}}
|
||||
assert("init" === (stateful !! GetMapState("testShouldRollbackStateForStatefulServerInCaseOfFailure")).get) // check that state is == init state
|
||||
}
|
||||
/*
|
||||
|
||||
@Test
|
||||
def shouldOneWayVectorShouldNotRollbackStateForStatefulServerInCaseOfSuccess = {
|
||||
val stateful = new InMemStatefulActor
|
||||
|
|
@ -145,7 +145,7 @@ class InMemoryActorTest extends JUnitSuite {
|
|||
Thread.sleep(1000)
|
||||
assert(2 === (stateful !! GetVectorSize).get)
|
||||
}
|
||||
*/
|
||||
|
||||
@Test
|
||||
def shouldVectorShouldNotRollbackStateForStatefulServerInCaseOfSuccess = {
|
||||
val stateful = new InMemStatefulActor
|
||||
|
|
@ -154,7 +154,7 @@ class InMemoryActorTest extends JUnitSuite {
|
|||
stateful !! Success("testShouldNotRollbackStateForStatefulServerInCaseOfSuccess", "new state") // transactionrequired
|
||||
assert(2 === (stateful !! GetVectorSize).get)
|
||||
}
|
||||
/*
|
||||
|
||||
@Test
|
||||
def shouldOneWayVectorShouldRollbackStateForStatefulServerInCaseOfFailure = {
|
||||
val stateful = new InMemStatefulActor
|
||||
|
|
@ -167,7 +167,7 @@ class InMemoryActorTest extends JUnitSuite {
|
|||
Thread.sleep(1000)
|
||||
assert(1 === (stateful !! GetVectorSize).get)
|
||||
}
|
||||
*/
|
||||
|
||||
@Test
|
||||
def shouldVectorShouldRollbackStateForStatefulServerInCaseOfFailure = {
|
||||
val stateful = new InMemStatefulActor
|
||||
|
|
@ -181,7 +181,7 @@ class InMemoryActorTest extends JUnitSuite {
|
|||
} catch {case e: RuntimeException => {}}
|
||||
assert(1 === (stateful !! GetVectorSize).get)
|
||||
}
|
||||
/*
|
||||
|
||||
@Test
|
||||
def shouldOneWayRefShouldNotRollbackStateForStatefulServerInCaseOfSuccess = {
|
||||
val stateful = new InMemStatefulActor
|
||||
|
|
@ -192,7 +192,7 @@ class InMemoryActorTest extends JUnitSuite {
|
|||
Thread.sleep(1000)
|
||||
assert("new state" === (stateful !! GetRefState).get)
|
||||
}
|
||||
*/
|
||||
|
||||
@Test
|
||||
def shouldRefShouldNotRollbackStateForStatefulServerInCaseOfSuccess = {
|
||||
val stateful = new InMemStatefulActor
|
||||
|
|
@ -201,7 +201,7 @@ class InMemoryActorTest extends JUnitSuite {
|
|||
stateful !! Success("testShouldNotRollbackStateForStatefulServerInCaseOfSuccess", "new state") // transactionrequired
|
||||
assert("new state" === (stateful !! GetRefState).get)
|
||||
}
|
||||
/*
|
||||
|
||||
@Test
|
||||
def shouldOneWayRefShouldRollbackStateForStatefulServerInCaseOfFailure = {
|
||||
val stateful = new InMemStatefulActor
|
||||
|
|
@ -212,9 +212,9 @@ class InMemoryActorTest extends JUnitSuite {
|
|||
failer.start
|
||||
stateful ! FailureOneWay("testShouldRollbackStateForStatefulServerInCaseOfFailure", "new state", failer) // call failing transactionrequired method
|
||||
Thread.sleep(1000)
|
||||
assert("init" === (stateful !! GetRefState).get) // check that state is == init state
|
||||
assert("init" === (stateful !! (GetRefState, 1000000)).get) // check that state is == init state
|
||||
}
|
||||
*/
|
||||
|
||||
@Test
|
||||
def shouldRefShouldRollbackStateForStatefulServerInCaseOfFailure = {
|
||||
val stateful = new InMemStatefulActor
|
||||
|
|
|
|||
|
|
@ -22,9 +22,11 @@ class MemoryFootprintTest extends JUnitSuite {
|
|||
// Actors are put in AspectRegistry when created so they won't be GCd here
|
||||
|
||||
val totalMem = Runtime.getRuntime.totalMemory - Runtime.getRuntime.freeMemory
|
||||
println("Memory before " + totalMem)
|
||||
(1 until NR_OF_ACTORS).foreach(i => new Mem)
|
||||
|
||||
val newTotalMem = Runtime.getRuntime.totalMemory - Runtime.getRuntime.freeMemory
|
||||
println("Memory aftor " + newTotalMem)
|
||||
val memPerActor = (newTotalMem - totalMem) / NR_OF_ACTORS
|
||||
|
||||
println("Memory footprint per actor is : " + memPerActor)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package test
|
||||
package se.scalablesolutions.akka
|
||||
|
||||
import org.scalatest.junit.JUnitSuite
|
||||
import org.junit.Test
|
||||
|
|
@ -31,12 +31,12 @@ class PerformanceTest extends JUnitSuite {
|
|||
|
||||
case class Meet(from: Actor, colour: Colour)
|
||||
case class Change(colour: Colour)
|
||||
case class MeetingCount(count: int)
|
||||
case class MeetingCount(count: Int)
|
||||
case class ExitActor(actor: Actor, reason: String)
|
||||
|
||||
var totalTime = 0L
|
||||
|
||||
class Mall(var nrMeets: int, numChameneos: int) extends Actor {
|
||||
class Mall(var nrMeets: Int, numChameneos: Int) extends Actor {
|
||||
var waitingChameneo: Option[Actor] = None
|
||||
var sumMeetings = 0
|
||||
var numFaded = 0
|
||||
|
|
@ -86,7 +86,7 @@ class PerformanceTest extends JUnitSuite {
|
|||
}
|
||||
}
|
||||
|
||||
case class Chameneo(var mall: Mall, var colour: Colour, cid: int) extends Actor {
|
||||
case class Chameneo(var mall: Mall, var colour: Colour, cid: Int) extends Actor {
|
||||
var meetings = 0
|
||||
|
||||
override def start = {
|
||||
|
|
@ -160,10 +160,10 @@ class PerformanceTest extends JUnitSuite {
|
|||
|
||||
case class Meet(colour: Colour)
|
||||
case class Change(colour: Colour)
|
||||
case class MeetingCount(count: int)
|
||||
case class MeetingCount(count: Int)
|
||||
|
||||
|
||||
class Mall(var n: int, numChameneos: int) extends Actor {
|
||||
class Mall(var n: Int, numChameneos: Int) extends Actor {
|
||||
var waitingChameneo: Option[OutputChannel[Any]] = None
|
||||
var startTime: Long = 0L
|
||||
|
||||
|
|
@ -218,7 +218,7 @@ class PerformanceTest extends JUnitSuite {
|
|||
}
|
||||
}
|
||||
|
||||
case class Chameneo(var mall: Mall, var colour: Colour, id: int) extends Actor {
|
||||
case class Chameneo(var mall: Mall, var colour: Colour, id: Int) extends Actor {
|
||||
var meetings = 0
|
||||
|
||||
def act() {
|
||||
|
|
@ -279,7 +279,7 @@ class PerformanceTest extends JUnitSuite {
|
|||
|
||||
var nrOfMessages = 2000000
|
||||
var nrOfActors = 4
|
||||
var akkaTime = stressTestAkkaActors(nrOfMessages, nrOfActors, 1000 * 20)
|
||||
var akkaTime = stressTestAkkaActors(nrOfMessages, nrOfActors, 1000 * 30)
|
||||
var scalaTime = stressTestScalaActors(nrOfMessages, nrOfActors, 1000 * 40)
|
||||
var ratio: Double = scalaTime.toDouble / akkaTime.toDouble
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import Actor.Sender.Self
|
|||
|
||||
import org.scalatest.junit.JUnitSuite
|
||||
import org.junit.Test
|
||||
|
||||
/*
|
||||
class RemoteClientShutdownTest extends JUnitSuite {
|
||||
@Test def shouldShutdownRemoteClient = {
|
||||
RemoteNode.start("localhost", 9999)
|
||||
|
|
@ -28,3 +28,4 @@ class TravelingActor extends RemoteActor("localhost", 9999) {
|
|||
case _ => log.info("message received")
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
@ -74,7 +74,7 @@ object Log {
|
|||
class RemoteSupervisorTest extends JUnitSuite {
|
||||
import Actor.Sender.Self
|
||||
|
||||
akka.Config.config
|
||||
akka.config.Config.config
|
||||
new Thread(new Runnable() {
|
||||
def run = {
|
||||
RemoteNode.start
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ class ServerInitiatedRemoteActorTest extends JUnitSuite {
|
|||
import ServerInitiatedRemoteActorTest._
|
||||
|
||||
import Actor.Sender.Self
|
||||
akka.Config.config
|
||||
akka.config.Config.config
|
||||
|
||||
private val unit = TimeUnit.MILLISECONDS
|
||||
|
||||
|
|
|
|||
|
|
@ -2,9 +2,8 @@ package se.scalablesolutions.akka.remote
|
|||
|
||||
import se.scalablesolutions.akka.actor.Actor
|
||||
|
||||
object ActorShutdownSpec {
|
||||
object ActorShutdownRunner {
|
||||
def main(args: Array[String]) {
|
||||
|
||||
class MyActor extends Actor {
|
||||
def receive = {
|
||||
case "test" => println("received test")
|
||||
|
|
@ -22,7 +21,7 @@ object ActorShutdownSpec {
|
|||
|
||||
// case 2
|
||||
|
||||
object RemoteServerAndClusterShutdownSpec {
|
||||
object RemoteServerAndClusterShutdownRunner {
|
||||
def main(args: Array[String]) {
|
||||
val s1 = new RemoteServer
|
||||
val s2 = new RemoteServer
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ class ThreadBasedDispatcherTest extends JUnitSuite {
|
|||
})
|
||||
dispatcher.start
|
||||
for (i <- 0 until 100) {
|
||||
dispatcher.dispatch(new MessageInvocation(key1, new Integer(i), None, None, None))
|
||||
dispatcher.dispatch(new MessageInvocation(key1, i, None, None, None))
|
||||
}
|
||||
assert(handleLatch.await(5, TimeUnit.SECONDS))
|
||||
assert(!threadingIssueDetected.get)
|
||||
|
|
|
|||
33
akka-fun-test-java/pom.xml
Normal file → Executable file
33
akka-fun-test-java/pom.xml
Normal file → Executable file
|
|
@ -5,31 +5,43 @@
|
|||
|
||||
<name>Akka Functional Tests in Java</name>
|
||||
<artifactId>akka-fun-test-java</artifactId>
|
||||
|
||||
<groupId>se.scalablesolutions.akka</groupId>
|
||||
<version>0.7-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<artifactId>akka</artifactId>
|
||||
<groupId>se.scalablesolutions.akka</groupId>
|
||||
<version>0.7-SNAPSHOT</version>
|
||||
</parent>
|
||||
<properties>
|
||||
<scala.version>2.7.7</scala.version>
|
||||
<atmosphere.version>0.5.2</atmosphere.version>
|
||||
<jersey.version>1.1.5</jersey.version>
|
||||
<grizzly.version>1.9.18-i</grizzly.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<artifactId>akka-kernel</artifactId>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<version>${project.version}</version>
|
||||
<groupId>se.scalablesolutions.akka</groupId>
|
||||
<version>0.7-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<groupId>se.scalablesolutions.akka</groupId>
|
||||
<artifactId>akka-persistence-cassandra</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<version>0.7-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.protobuf</groupId>
|
||||
<artifactId>protobuf-java</artifactId>
|
||||
<version>2.2.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-core-asl</artifactId>
|
||||
<version>1.2.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-mapper-asl</artifactId>
|
||||
<version>1.2.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.sun.grizzly</groupId>
|
||||
<artifactId>grizzly-servlet-webserver</artifactId>
|
||||
|
|
@ -94,7 +106,6 @@
|
|||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<excludes>
|
||||
<exclude>**/InMemNestedStateTest*</exclude>
|
||||
<exclude>**/*Persistent*</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import com.google.inject.Scopes;
|
|||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import se.scalablesolutions.akka.Config;
|
||||
import se.scalablesolutions.akka.config.Config;
|
||||
import se.scalablesolutions.akka.config.ActiveObjectConfigurator;
|
||||
import static se.scalablesolutions.akka.config.JavaConfig.*;
|
||||
import se.scalablesolutions.akka.dispatch.*;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package se.scalablesolutions.akka.api;
|
||||
|
||||
import se.scalablesolutions.akka.annotation.oneway;
|
||||
import se.scalablesolutions.akka.actor.annotation.oneway;
|
||||
|
||||
public interface Bar {
|
||||
@oneway
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package se.scalablesolutions.akka.api;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import se.scalablesolutions.akka.annotation.oneway;
|
||||
import se.scalablesolutions.akka.actor.annotation.oneway;
|
||||
|
||||
public class Foo extends se.scalablesolutions.akka.serialization.Serializable.JavaJSON {
|
||||
@Inject
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@
|
|||
|
||||
package se.scalablesolutions.akka.api;
|
||||
|
||||
import se.scalablesolutions.akka.Config;
|
||||
import se.scalablesolutions.akka.config.*;
|
||||
import se.scalablesolutions.akka.config.Config;
|
||||
import se.scalablesolutions.akka.config.ActiveObjectConfigurator;
|
||||
import static se.scalablesolutions.akka.config.JavaConfig.*;
|
||||
import se.scalablesolutions.akka.actor.*;
|
||||
import se.scalablesolutions.akka.Kernel;
|
||||
import se.scalablesolutions.akka.kernel.Kernel;
|
||||
import junit.framework.TestCase;
|
||||
/*
|
||||
|
||||
public class InMemNestedStateTest extends TestCase {
|
||||
static String messageLog = "";
|
||||
|
||||
|
|
@ -133,4 +133,3 @@ public class InMemNestedStateTest extends TestCase {
|
|||
assertEquals("init", nested.getRefState()); // check that state is == init state
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
package se.scalablesolutions.akka.api;
|
||||
|
||||
import se.scalablesolutions.akka.annotation.transactionrequired;
|
||||
import se.scalablesolutions.akka.annotation.prerestart;
|
||||
import se.scalablesolutions.akka.annotation.postrestart;
|
||||
import se.scalablesolutions.akka.annotation.inittransactionalstate;
|
||||
import se.scalablesolutions.akka.state.*;
|
||||
import se.scalablesolutions.akka.actor.annotation.transactionrequired;
|
||||
import se.scalablesolutions.akka.actor.annotation.prerestart;
|
||||
import se.scalablesolutions.akka.actor.annotation.postrestart;
|
||||
import se.scalablesolutions.akka.actor.annotation.inittransactionalstate;
|
||||
import se.scalablesolutions.akka.stm.*;
|
||||
|
||||
@transactionrequired
|
||||
public class InMemStateful {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package se.scalablesolutions.akka.api;
|
||||
|
||||
import se.scalablesolutions.akka.annotation.transactionrequired;
|
||||
import se.scalablesolutions.akka.annotation.inittransactionalstate;
|
||||
import se.scalablesolutions.akka.state.*;
|
||||
import se.scalablesolutions.akka.actor.annotation.transactionrequired;
|
||||
import se.scalablesolutions.akka.actor.annotation.inittransactionalstate;
|
||||
import se.scalablesolutions.akka.stm.*;
|
||||
|
||||
@transactionrequired
|
||||
public class InMemStatefulNested {
|
||||
|
|
|
|||
|
|
@ -6,14 +6,14 @@ package se.scalablesolutions.akka.api;
|
|||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import se.scalablesolutions.akka.Config;
|
||||
import se.scalablesolutions.akka.config.Config;
|
||||
import se.scalablesolutions.akka.config.*;
|
||||
import se.scalablesolutions.akka.config.ActiveObjectConfigurator;
|
||||
|
||||
import static se.scalablesolutions.akka.config.JavaConfig.*;
|
||||
|
||||
import se.scalablesolutions.akka.actor.*;
|
||||
import se.scalablesolutions.akka.Kernel;
|
||||
import se.scalablesolutions.akka.kernel.Kernel;
|
||||
|
||||
public class InMemoryStateTest extends TestCase {
|
||||
static String messageLog = "";
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ public class PersistenceManager {
|
|||
private static volatile boolean isRunning = false;
|
||||
public static void init() {
|
||||
if (!isRunning) {
|
||||
se.scalablesolutions.akka.Kernel$.MODULE$.startRemoteService();
|
||||
se.scalablesolutions.akka.kernel.Kernel$.MODULE$.startRemoteService();
|
||||
isRunning = true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
package se.scalablesolutions.akka.api;
|
||||
|
||||
import se.scalablesolutions.akka.state.*;
|
||||
import se.scalablesolutions.akka.annotation.inittransactionalstate;
|
||||
import se.scalablesolutions.akka.persistence.common.*;
|
||||
import se.scalablesolutions.akka.persistence.cassandra.*;
|
||||
import se.scalablesolutions.akka.actor.annotation.inittransactionalstate;
|
||||
|
||||
public class PersistentClasher {
|
||||
private PersistentMap state;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import se.scalablesolutions.akka.config.*;
|
|||
import se.scalablesolutions.akka.config.ActiveObjectConfigurator;
|
||||
import static se.scalablesolutions.akka.config.JavaConfig.*;
|
||||
import se.scalablesolutions.akka.actor.*;
|
||||
import se.scalablesolutions.akka.Kernel;
|
||||
import se.scalablesolutions.akka.kernel.Kernel;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
package se.scalablesolutions.akka.api;
|
||||
|
||||
import se.scalablesolutions.akka.annotation.inittransactionalstate;
|
||||
import se.scalablesolutions.akka.annotation.transactionrequired;
|
||||
import se.scalablesolutions.akka.state.*;
|
||||
import se.scalablesolutions.akka.actor.annotation.inittransactionalstate;
|
||||
import se.scalablesolutions.akka.actor.annotation.transactionrequired;
|
||||
import se.scalablesolutions.akka.persistence.common.*;
|
||||
import se.scalablesolutions.akka.persistence.cassandra.*;
|
||||
|
||||
@transactionrequired
|
||||
public class PersistentStateful {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
package se.scalablesolutions.akka.api;
|
||||
|
||||
import se.scalablesolutions.akka.annotation.inittransactionalstate;
|
||||
import se.scalablesolutions.akka.annotation.transactionrequired;
|
||||
import se.scalablesolutions.akka.state.*;
|
||||
import se.scalablesolutions.akka.actor.annotation.inittransactionalstate;
|
||||
import se.scalablesolutions.akka.actor.annotation.transactionrequired;
|
||||
import se.scalablesolutions.akka.persistence.common.*;
|
||||
import se.scalablesolutions.akka.persistence.cassandra.*;
|
||||
|
||||
@transactionrequired
|
||||
public class PersistentStatefulNested {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
package se.scalablesolutions.akka.api;
|
||||
|
||||
import se.scalablesolutions.akka.Config;
|
||||
import se.scalablesolutions.akka.config.Config;
|
||||
import se.scalablesolutions.akka.actor.ActiveObject;
|
||||
import se.scalablesolutions.akka.config.ActiveObjectConfigurator;
|
||||
import se.scalablesolutions.akka.remote.RemoteNode;
|
||||
|
|
|
|||
|
|
@ -1,136 +0,0 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>akka-kernel</artifactId>
|
||||
<name>Akka Kernel Module</name>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<artifactId>akka</artifactId>
|
||||
<groupId>se.scalablesolutions.akka</groupId>
|
||||
<version>0.7-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<!-- akka deps -->
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<artifactId>akka-rest</artifactId>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<artifactId>akka-amqp</artifactId>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<artifactId>akka-security</artifactId>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<artifactId>akka-persistence-cassandra</artifactId>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<artifactId>akka-persistence-mongo</artifactId>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<artifactId>akka-persistence-redis</artifactId>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<artifactId>akka-comet</artifactId>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<artifactId>akka-cluster-jgroups</artifactId>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<!--dependency>
|
||||
<artifactId>akka-cluster-shoal</artifactId>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<version>${project.version}</version>
|
||||
</dependency-->
|
||||
|
||||
<!-- For Atmosphere -->
|
||||
<dependency>
|
||||
<groupId>com.sun.jersey</groupId>
|
||||
<artifactId>jersey-server</artifactId>
|
||||
<version>${jersey.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.atmosphere</groupId>
|
||||
<artifactId>atmosphere-annotations</artifactId>
|
||||
<version>${atmosphere.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.atmosphere</groupId>
|
||||
<artifactId>atmosphere-jersey</artifactId>
|
||||
<version>${atmosphere.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.atmosphere</groupId>
|
||||
<artifactId>atmosphere-runtime</artifactId>
|
||||
<version>${atmosphere.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>1.2.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>install</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<artifactSet>
|
||||
<excludes>
|
||||
<exclude>junit:junit</exclude>
|
||||
</excludes>
|
||||
</artifactSet>
|
||||
<transformers>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ComponentsXmlResourceTransformer"/>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<mainClass>se.scalablesolutions.akka.Main</mainClass>
|
||||
</transformer>
|
||||
</transformers>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-antrun-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>install</phase>
|
||||
<configuration>
|
||||
<tasks>
|
||||
<copy file="target/akka-kernel-${project.version}.jar"
|
||||
tofile="../dist/akka-${project.version}.jar"/>
|
||||
</tasks>
|
||||
</configuration>
|
||||
<goals>
|
||||
<goal>run</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
|
@ -2,11 +2,14 @@
|
|||
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
||||
*/
|
||||
|
||||
package se.scalablesolutions.akka
|
||||
package se.scalablesolutions.akka.kernel
|
||||
|
||||
import se.scalablesolutions.akka.remote.BootableRemoteActorService
|
||||
import se.scalablesolutions.akka.comet.BootableCometActorService
|
||||
import se.scalablesolutions.akka.actor.BootableActorLoaderService
|
||||
import se.scalablesolutions.akka.util.{Logging,Bootable}
|
||||
import se.scalablesolutions.akka.camel.service.CamelService
|
||||
import se.scalablesolutions.akka.config.Config
|
||||
import se.scalablesolutions.akka.util.{Logging, Bootable}
|
||||
|
||||
import javax.servlet.{ServletContextListener, ServletContextEvent}
|
||||
|
||||
|
|
@ -27,12 +30,16 @@ object Kernel extends Logging {
|
|||
/**
|
||||
* Holds a reference to the services that has been booted
|
||||
*/
|
||||
@volatile private var bundles : Option[Bootable] = None
|
||||
@volatile private var bundles: Option[Bootable] = None
|
||||
|
||||
/**
|
||||
* Boots up the Kernel with default bootables
|
||||
* Boots up the Kernel with default bootables
|
||||
*/
|
||||
def boot : Unit = boot(true, new BootableActorLoaderService with BootableRemoteActorService with BootableCometActorService)
|
||||
def boot: Unit = boot(true,
|
||||
new BootableActorLoaderService
|
||||
with BootableRemoteActorService
|
||||
with BootableCometActorService
|
||||
with CamelService)
|
||||
|
||||
/**
|
||||
* Boots up the Kernel.
|
||||
|
|
@ -63,8 +70,8 @@ object Kernel extends Logging {
|
|||
}
|
||||
|
||||
//For testing purposes only
|
||||
def startRemoteService : Unit = bundles.foreach( _ match {
|
||||
case x : BootableRemoteActorService => x.startRemoteService
|
||||
def startRemoteService: Unit = bundles.foreach( _ match {
|
||||
case x: BootableRemoteActorService => x.startRemoteService
|
||||
case _ =>
|
||||
})
|
||||
|
||||
|
|
@ -79,16 +86,18 @@ object Kernel extends Logging {
|
|||
(____ /__|_ \__|_ \(____ /
|
||||
\/ \/ \/ \/
|
||||
""")
|
||||
log.info(" Running version %s", Config.VERSION)
|
||||
log.info(" Running version %s", Config.VERSION)
|
||||
log.info("==============================")
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
And this one can be added to web.xml mappings as a listener to boot and shutdown Akka
|
||||
*/
|
||||
|
||||
/**
|
||||
* This class can be added to web.xml mappings as a listener to boot and shutdown Akka.
|
||||
*/
|
||||
class Kernel extends ServletContextListener {
|
||||
def contextDestroyed(e : ServletContextEvent) : Unit = Kernel.shutdown
|
||||
def contextInitialized(e : ServletContextEvent) : Unit = Kernel.boot(true,new BootableActorLoaderService with BootableRemoteActorService)
|
||||
def contextDestroyed(e: ServletContextEvent): Unit =
|
||||
Kernel.shutdown
|
||||
|
||||
def contextInitialized(e: ServletContextEvent): Unit =
|
||||
Kernel.boot(true, new BootableActorLoaderService with BootableRemoteActorService)
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>akka-patterns</artifactId>
|
||||
<name>Akka Patterns Module</name>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<artifactId>akka</artifactId>
|
||||
<groupId>se.scalablesolutions.akka</groupId>
|
||||
<version>0.7-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<!-- Core deps -->
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<artifactId>akka-core</artifactId>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- For Testing -->
|
||||
<dependency>
|
||||
<groupId>org.scalatest</groupId>
|
||||
<artifactId>scalatest</artifactId>
|
||||
<version>1.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.5</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
@ -1,146 +0,0 @@
|
|||
// ScalaAgent
|
||||
//
|
||||
// Copyright © 2008-9 The original author or authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package se.scalablesolutions.akka.actor
|
||||
|
||||
import se.scalablesolutions.akka.state.TransactionalState
|
||||
import se.scalablesolutions.akka.stm.Transaction.atomic
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import java.util.concurrent.{CountDownLatch}
|
||||
|
||||
/**
|
||||
* The Agent class was strongly inspired by the agent principle in Clojure. Essentially, an agent wraps a shared mutable state
|
||||
* and hides it behind a message-passing interface. Agents accept messages and process them on behalf of the wrapped state.
|
||||
* Typically agents accept functions / commands as messages and ensure the submitted commands are executed against the internal
|
||||
* agent's state in a thread-safe manner (sequentially).
|
||||
* The submitted functions / commands take the internal state as a parameter and their output becomes the new internal state value.
|
||||
* The code that is submitted to an agent doesn't need to pay attention to threading or synchronization, the agent will
|
||||
* provide such guarantees by itself.
|
||||
* See the examples of use for more details.
|
||||
*
|
||||
* @author Vaclav Pech
|
||||
* Date: Oct 18, 2009
|
||||
*
|
||||
* AKKA retrofit by
|
||||
* @author Viktor Klang
|
||||
* Date: Jan 24 2010
|
||||
*/
|
||||
sealed class Agent[T] private (initialValue: T) extends Actor {
|
||||
import Agent._
|
||||
|
||||
private val value = TransactionalState.newRef[T]
|
||||
|
||||
updateData(initialValue)
|
||||
|
||||
/**
|
||||
* Periodically handles incoming messages
|
||||
*/
|
||||
def receive = {
|
||||
case FunctionHolder(fun: (T => T)) => atomic { updateData(fun(value.getOrWait)) }
|
||||
|
||||
case ValueHolder(x: T) => updateData(x)
|
||||
|
||||
case ProcedureHolder(fun: (T => Unit)) => atomic { fun(copyStrategy(value.getOrWait)) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies how a copy of the value is made, defaults to using identity
|
||||
*/
|
||||
protected def copyStrategy(t : T) : T = t
|
||||
|
||||
|
||||
/**
|
||||
* Updates the internal state with the value provided as a by-name parameter
|
||||
*/
|
||||
private final def updateData(newData: => T) : Unit = atomic { value.swap(newData) }
|
||||
|
||||
/**
|
||||
* Submits a request to read the internal state.
|
||||
* A copy of the internal state will be returned, depending on the underlying effective copyStrategy.
|
||||
* Internally leverages the asynchronous getValue() method and then waits for its result on a CountDownLatch.
|
||||
*/
|
||||
final def get : T = {
|
||||
val ref = new AtomicReference[T]
|
||||
val latch = new CountDownLatch(1)
|
||||
get((x: T) => {ref.set(x); latch.countDown})
|
||||
latch.await
|
||||
ref.get
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously submits a request to read the internal state. The supplied function will be executed on the returned internal state value.
|
||||
* A copy of the internal state will be used, depending on the underlying effective copyStrategy.
|
||||
*/
|
||||
final def get(message: (T => Unit)) : Unit = this ! ProcedureHolder(message)
|
||||
|
||||
/**
|
||||
* Submits a request to read the internal state.
|
||||
* A copy of the internal state will be returned, depending on the underlying effective copyStrategy.
|
||||
* Internally leverages the asynchronous getValue() method and then waits for its result on a CountDownLatch.
|
||||
*/
|
||||
final def apply() : T = get
|
||||
|
||||
/**
|
||||
* Asynchronously submits a request to read the internal state. The supplied function will be executed on the returned internal state value.
|
||||
* A copy of the internal state will be used, depending on the underlying effective copyStrategy.
|
||||
*/
|
||||
// final def apply(message: (T => Unit)) : Unit = get(message)
|
||||
|
||||
/**
|
||||
* Submits the provided function for execution against the internal agent's state
|
||||
*/
|
||||
final def apply(message: (T => T)) : Unit = this ! FunctionHolder(message)
|
||||
|
||||
/**
|
||||
* Submits a new value to be set as the new agent's internal state
|
||||
*/
|
||||
final def apply(message: T) : Unit = this ! ValueHolder(message)
|
||||
|
||||
/**
|
||||
* Submits the provided function for execution against the internal agent's state
|
||||
*/
|
||||
final def update(message: (T => T)) : Unit = this ! FunctionHolder(message)
|
||||
|
||||
/**
|
||||
* Submits a new value to be set as the new agent's internal state
|
||||
*/
|
||||
final def update(message: T) : Unit = this ! ValueHolder(message)
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides factory methods to create Agents.
|
||||
*/
|
||||
object Agent {
|
||||
/**
|
||||
* The internal messages for passing around requests
|
||||
*/
|
||||
private case class ProcedureHolder[T](val fun: ((T) => Unit))
|
||||
private case class FunctionHolder[T](val fun: ((T) => T))
|
||||
private case class ValueHolder[T](val value: T)
|
||||
|
||||
/**
|
||||
* Creates a new Agent of type T with the initial value of value
|
||||
*/
|
||||
def apply[T](value:T) : Agent[T] = new Agent(value)
|
||||
|
||||
/**
|
||||
* Creates a new Agent of type T with the initial value of value and with the specified copy function
|
||||
*/
|
||||
def apply[T](value:T, newCopyStrategy: (T) => T) = new Agent(value) {
|
||||
override def copyStrategy(t : T) = newCopyStrategy(t)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,16 @@
|
|||
package se.scalablesolutions.akka.actor.patterns
|
||||
package se.scalablesolutions.akka.patterns
|
||||
|
||||
import se.scalablesolutions.akka.actor.Actor
|
||||
|
||||
object Patterns {
|
||||
type PF[A,B] = PartialFunction[A,B]
|
||||
type PF[A, B] = PartialFunction[A, B]
|
||||
|
||||
/**
|
||||
* Creates a new PartialFunction whose isDefinedAt is a combination
|
||||
* of the two parameters, and whose apply is first to call filter.apply and then filtered.apply
|
||||
*/
|
||||
def filter[A,B](filter : PF[A,Unit],filtered : PF[A,B]) : PF[A,B] = {
|
||||
case a : A if filtered.isDefinedAt(a) && filter.isDefinedAt(a) =>
|
||||
def filter[A, B](filter: PF[A, Unit], filtered: PF[A, B]): PF[A, B] = {
|
||||
case a: A if filtered.isDefinedAt(a) && filter.isDefinedAt(a) =>
|
||||
filter(a)
|
||||
filtered(a)
|
||||
}
|
||||
|
|
@ -18,61 +18,58 @@ object Patterns {
|
|||
/**
|
||||
* Interceptor is a filter(x,y) where x.isDefinedAt is considered to be always true
|
||||
*/
|
||||
def intercept[A,B](interceptor : (A) => Unit, interceptee : PF[A,B]) : PF[A,B] = filter(
|
||||
{ case a if a.isInstanceOf[A] => interceptor(a) },
|
||||
interceptee
|
||||
)
|
||||
|
||||
def intercept[A, B](interceptor: (A) => Unit, interceptee: PF[A, B]): PF[A, B] =
|
||||
filter({case a if a.isInstanceOf[A] => interceptor(a)}, interceptee)
|
||||
|
||||
//FIXME 2.8, use default params with CyclicIterator
|
||||
def loadBalancerActor(actors : => InfiniteIterator[Actor]) : Actor = new Actor with LoadBalancer {
|
||||
def loadBalancerActor(actors: => InfiniteIterator[Actor]): Actor = new Actor with LoadBalancer {
|
||||
val seq = actors
|
||||
}
|
||||
|
||||
def dispatcherActor(routing : PF[Any,Actor], msgTransformer : (Any) => Any) : Actor = new Actor with Dispatcher {
|
||||
override def transform(msg : Any) = msgTransformer(msg)
|
||||
def dispatcherActor(routing: PF[Any, Actor], msgTransformer: (Any) => Any): Actor =
|
||||
new Actor with Dispatcher {
|
||||
override def transform(msg: Any) = msgTransformer(msg)
|
||||
def routes = routing
|
||||
}
|
||||
|
||||
def dispatcherActor(routing : PF[Any,Actor]) : Actor = new Actor with Dispatcher {
|
||||
def routes = routing
|
||||
|
||||
def dispatcherActor(routing: PF[Any, Actor]): Actor = new Actor with Dispatcher {
|
||||
def routes = routing
|
||||
}
|
||||
|
||||
def loggerActor(actorToLog : Actor, logger : (Any) => Unit) : Actor = dispatcherActor (
|
||||
{ case _ => actorToLog },
|
||||
logger
|
||||
)
|
||||
def loggerActor(actorToLog: Actor, logger: (Any) => Unit): Actor =
|
||||
dispatcherActor({case _ => actorToLog}, logger)
|
||||
}
|
||||
|
||||
trait Dispatcher { self : Actor =>
|
||||
trait Dispatcher { self: Actor =>
|
||||
|
||||
protected def transform(msg : Any) : Any = msg
|
||||
protected def routes : PartialFunction[Any,Actor]
|
||||
|
||||
protected def dispatch : PartialFunction[Any,Unit] = {
|
||||
case a if routes.isDefinedAt(a) => {
|
||||
if(self.sender.isDefined)
|
||||
routes(a) forward transform(a)
|
||||
else
|
||||
routes(a) send transform(a)
|
||||
}
|
||||
protected def transform(msg: Any): Any = msg
|
||||
|
||||
protected def routes: PartialFunction[Any, Actor]
|
||||
|
||||
protected def dispatch: PartialFunction[Any, Unit] = {
|
||||
case a if routes.isDefinedAt(a) =>
|
||||
if (self.sender.isDefined) routes(a) forward transform(a)
|
||||
else routes(a) send transform(a)
|
||||
}
|
||||
|
||||
def receive = dispatch
|
||||
}
|
||||
|
||||
trait LoadBalancer extends Dispatcher { self : Actor =>
|
||||
protected def seq : InfiniteIterator[Actor]
|
||||
trait LoadBalancer extends Dispatcher { self: Actor =>
|
||||
protected def seq: InfiniteIterator[Actor]
|
||||
|
||||
protected def routes = { case x if seq.hasNext => seq.next }
|
||||
}
|
||||
|
||||
trait InfiniteIterator[T] extends Iterator[T]
|
||||
|
||||
class CyclicIterator[T](items : List[T]) extends InfiniteIterator[T] {
|
||||
@volatile private[this] var current : List[T] = items
|
||||
class CyclicIterator[T](items: List[T]) extends InfiniteIterator[T] {
|
||||
@volatile private[this] var current: List[T] = items
|
||||
|
||||
def hasNext = items != Nil
|
||||
|
||||
def next = {
|
||||
val nc = if(current == Nil) items else current
|
||||
val nc = if (current == Nil) items else current
|
||||
current = nc.tail
|
||||
nc.head
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
package se.scalablesolutions.akka.actor
|
||||
package se.scalablesolutions.akka.patterns
|
||||
|
||||
|
||||
import config.ScalaConfig._
|
||||
import se.scalablesolutions.akka.config.ScalaConfig._
|
||||
import se.scalablesolutions.akka.actor.Actor
|
||||
import se.scalablesolutions.akka.actor.Actor._
|
||||
import se.scalablesolutions.akka.util.Logging
|
||||
|
||||
import org.scalatest.Suite
|
||||
import patterns.Patterns
|
||||
import se.scalablesolutions.akka.util.Logging
|
||||
import org.junit.runner.RunWith
|
||||
import org.scalatest.junit.JUnitRunner
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
|
|
@ -14,19 +14,18 @@ import scala.collection.mutable.HashSet
|
|||
|
||||
@RunWith(classOf[JUnitRunner])
|
||||
class ActorPatternsTest extends junit.framework.TestCase with Suite with MustMatchers with ActorTestUtil with Logging {
|
||||
import Actor._
|
||||
import Patterns._
|
||||
@Test def testDispatcher = verify(new TestActor {
|
||||
def test = {
|
||||
val (testMsg1,testMsg2,testMsg3,testMsg4) = ("test1","test2","test3","test4")
|
||||
|
||||
var targetOk = 0
|
||||
val t1 = actor() receive {
|
||||
val t1: Actor = actor {
|
||||
case `testMsg1` => targetOk += 2
|
||||
case `testMsg2` => targetOk += 4
|
||||
}
|
||||
|
||||
val t2 = actor() receive {
|
||||
val t2: Actor = actor {
|
||||
case `testMsg3` => targetOk += 8
|
||||
}
|
||||
|
||||
|
|
@ -48,7 +47,7 @@ class ActorPatternsTest extends junit.framework.TestCase with Suite with MustMat
|
|||
@Test def testLogger = verify(new TestActor {
|
||||
def test = {
|
||||
val msgs = new HashSet[Any]
|
||||
val t1 = actor() receive {
|
||||
val t1: Actor = actor {
|
||||
case _ =>
|
||||
}
|
||||
val l = loggerActor(t1,(x) => msgs += x)
|
||||
|
|
|
|||
|
|
@ -1,86 +0,0 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>akka-persistence-cassandra</artifactId>
|
||||
<name>Akka Persistence Cassandra Module</name>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<artifactId>akka-persistence-parent</artifactId>
|
||||
<groupId>se.scalablesolutions.akka</groupId>
|
||||
<version>0.7-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<artifactId>akka-persistence-common</artifactId>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<version>${project.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.google.code.google-collections</groupId>
|
||||
<artifactId>google-collect</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- For Cassandra -->
|
||||
<dependency>
|
||||
<groupId>org.apache.cassandra</groupId>
|
||||
<artifactId>cassandra</artifactId>
|
||||
<version>0.5.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cassandra</groupId>
|
||||
<artifactId>high-scale-lib</artifactId>
|
||||
<version>0.5.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cassandra</groupId>
|
||||
<artifactId>clhm-production</artifactId>
|
||||
<version>0.5.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.collections</groupId>
|
||||
<artifactId>google-collections</artifactId>
|
||||
<version>1.0-rc1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-collections</groupId>
|
||||
<artifactId>commons-collections</artifactId>
|
||||
<version>3.2.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-lang</groupId>
|
||||
<artifactId>commons-lang</artifactId>
|
||||
<version>2.4</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>1.5.8</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-log4j12</artifactId>
|
||||
<version>1.5.8</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>log4j</groupId>
|
||||
<artifactId>log4j</artifactId>
|
||||
<version>1.2.13</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
|
@ -2,14 +2,15 @@
|
|||
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
||||
*/
|
||||
|
||||
package se.scalablesolutions.akka.state
|
||||
package se.scalablesolutions.akka.persistence.cassandra
|
||||
|
||||
import java.io.{Flushable, Closeable}
|
||||
|
||||
import se.scalablesolutions.akka.persistence.common._
|
||||
import se.scalablesolutions.akka.util.Logging
|
||||
import se.scalablesolutions.akka.util.Helpers._
|
||||
import se.scalablesolutions.akka.serialization.Serializer
|
||||
import se.scalablesolutions.akka.Config.config
|
||||
import se.scalablesolutions.akka.config.Config.config
|
||||
|
||||
import scala.collection.mutable.Map
|
||||
|
||||
|
|
|
|||
|
|
@ -2,16 +2,18 @@
|
|||
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
||||
*/
|
||||
|
||||
package se.scalablesolutions.akka.state
|
||||
package se.scalablesolutions.akka.persistence.cassandra
|
||||
|
||||
import org.codehaus.aspectwerkz.proxy.Uuid
|
||||
import se.scalablesolutions.akka.util.UUID
|
||||
import se.scalablesolutions.akka.stm._
|
||||
import se.scalablesolutions.akka.persistence.common._
|
||||
|
||||
object CassandraStorage extends Storage {
|
||||
type ElementType = Array[Byte]
|
||||
|
||||
def newMap: PersistentMap[ElementType, ElementType] = newMap(Uuid.newUuid.toString)
|
||||
def newVector: PersistentVector[ElementType] = newVector(Uuid.newUuid.toString)
|
||||
def newRef: PersistentRef[ElementType] = newRef(Uuid.newUuid.toString)
|
||||
def newMap: PersistentMap[ElementType, ElementType] = newMap(UUID.newUuid.toString)
|
||||
def newVector: PersistentVector[ElementType] = newVector(UUID.newUuid.toString)
|
||||
def newRef: PersistentRef[ElementType] = newRef(UUID.newUuid.toString)
|
||||
|
||||
def getMap(id: String): PersistentMap[ElementType, ElementType] = newMap(id)
|
||||
def getVector(id: String): PersistentVector[ElementType] = newVector(id)
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@
|
|||
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
||||
*/
|
||||
|
||||
package se.scalablesolutions.akka.state
|
||||
package se.scalablesolutions.akka.persistence.cassandra
|
||||
|
||||
import se.scalablesolutions.akka.stm._
|
||||
import se.scalablesolutions.akka.persistence.common._
|
||||
import se.scalablesolutions.akka.util.Logging
|
||||
import se.scalablesolutions.akka.util.Helpers._
|
||||
import se.scalablesolutions.akka.Config.config
|
||||
import se.scalablesolutions.akka.config.Config.config
|
||||
|
||||
import org.apache.cassandra.service._
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,9 @@
|
|||
package se.scalablesolutions.akka.state
|
||||
package se.scalablesolutions.akka.persistence.cassandra
|
||||
|
||||
import se.scalablesolutions.akka.actor.Actor
|
||||
|
||||
import junit.framework.TestCase
|
||||
import se.scalablesolutions.akka.actor.{Actor, Transactor}
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.Assert._
|
||||
import org.apache.cassandra.service.CassandraDaemon
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.Before
|
||||
import org.scalatest.junit.JUnitSuite
|
||||
|
||||
|
|
@ -28,9 +24,8 @@ case class SetRefStateOneWay(key: String)
|
|||
case class SuccessOneWay(key: String, value: String)
|
||||
case class FailureOneWay(key: String, value: String, failer: Actor)
|
||||
|
||||
class CassandraPersistentActor extends Actor {
|
||||
class CassandraPersistentActor extends Transactor {
|
||||
timeout = 100000
|
||||
makeTransactionRequired
|
||||
|
||||
private lazy val mapState = CassandraStorage.newMap
|
||||
private lazy val vectorState = CassandraStorage.newVector
|
||||
|
|
@ -66,8 +61,7 @@ class CassandraPersistentActor extends Actor {
|
|||
}
|
||||
}
|
||||
|
||||
@serializable class PersistentFailerActor extends Actor {
|
||||
makeTransactionRequired
|
||||
@serializable class PersistentFailerActor extends Transactor {
|
||||
def receive = {
|
||||
case "Failure" =>
|
||||
throw new RuntimeException("expected")
|
||||
|
|
@ -76,8 +70,8 @@ class CassandraPersistentActor extends Actor {
|
|||
|
||||
class CassandraPersistentActorSpec extends JUnitSuite {
|
||||
|
||||
@Before
|
||||
def startCassandra = EmbeddedCassandraService.start
|
||||
//@Before
|
||||
//def startCassandra = EmbeddedCassandraService.start
|
||||
|
||||
@Test
|
||||
def testMapShouldNotRollbackStateForStatefulServerInCaseOfSuccess = {
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>akka-persistence-common</artifactId>
|
||||
<name>Akka Persistence Common Module</name>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<artifactId>akka-persistence-parent</artifactId>
|
||||
<groupId>se.scalablesolutions.akka</groupId>
|
||||
<version>0.7-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.facebook</groupId>
|
||||
<artifactId>thrift</artifactId>
|
||||
<version>1.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-pool</groupId>
|
||||
<artifactId>commons-pool</artifactId>
|
||||
<version>1.5.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
||||
*/
|
||||
|
||||
package se.scalablesolutions.akka.state
|
||||
package se.scalablesolutions.akka.persistence.common
|
||||
|
||||
import org.apache.commons.pool._
|
||||
import org.apache.commons.pool.impl._
|
||||
|
|
|
|||
|
|
@ -2,16 +2,17 @@
|
|||
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
||||
*/
|
||||
|
||||
package se.scalablesolutions.akka.state
|
||||
package se.scalablesolutions.akka.persistence.common
|
||||
|
||||
import se.scalablesolutions.akka.stm.TransactionManagement.currentTransaction
|
||||
import se.scalablesolutions.akka.collection._
|
||||
import se.scalablesolutions.akka.stm._
|
||||
import se.scalablesolutions.akka.stm.TransactionManagement.transaction
|
||||
import se.scalablesolutions.akka.util.Logging
|
||||
|
||||
import org.codehaus.aspectwerkz.proxy.Uuid
|
||||
|
||||
// FIXME move to 'stm' package + add message with more info
|
||||
class NoTransactionInScopeException extends RuntimeException
|
||||
|
||||
class StorageException(message: String) extends RuntimeException(message)
|
||||
|
||||
/**
|
||||
* Example Scala usage.
|
||||
* <p/>
|
||||
|
|
@ -50,24 +51,26 @@ trait Storage {
|
|||
def newRef: PersistentRef[ElementType]
|
||||
def newQueue: PersistentQueue[ElementType] = // only implemented for redis
|
||||
throw new UnsupportedOperationException
|
||||
def newSortedSet: PersistentSortedSet[ElementType] = // only implemented for redis
|
||||
throw new UnsupportedOperationException
|
||||
|
||||
def getMap(id: String): PersistentMap[ElementType, ElementType]
|
||||
def getVector(id: String): PersistentVector[ElementType]
|
||||
def getRef(id: String): PersistentRef[ElementType]
|
||||
def getQueue(id: String): PersistentQueue[ElementType] = // only implemented for redis
|
||||
throw new UnsupportedOperationException
|
||||
def getSortedSet(id: String): PersistentSortedSet[ElementType] = // only implemented for redis
|
||||
throw new UnsupportedOperationException
|
||||
|
||||
def newMap(id: String): PersistentMap[ElementType, ElementType]
|
||||
def newVector(id: String): PersistentVector[ElementType]
|
||||
def newRef(id: String): PersistentRef[ElementType]
|
||||
def newQueue(id: String): PersistentQueue[ElementType] = // only implemented for redis
|
||||
throw new UnsupportedOperationException
|
||||
def newSortedSet(id: String): PersistentSortedSet[ElementType] = // only implemented for redis
|
||||
throw new UnsupportedOperationException
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of <tt>PersistentMap</tt> for every concrete
|
||||
* storage will have the same workflow. This abstracts the workflow.
|
||||
|
|
@ -162,8 +165,8 @@ trait PersistentMap[K, V] extends scala.collection.mutable.Map[K, V]
|
|||
}
|
||||
|
||||
private def register = {
|
||||
if (currentTransaction.get.isEmpty) throw new NoTransactionInScopeException
|
||||
currentTransaction.get.get.register(uuid, this)
|
||||
if (transaction.get.isEmpty) throw new NoTransactionInScopeException
|
||||
transaction.get.get.register(uuid, this)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -236,8 +239,8 @@ trait PersistentVector[T] extends RandomAccessSeq[T] with Transactional with Com
|
|||
def length: Int = storage.getVectorStorageSizeFor(uuid) + newElems.length
|
||||
|
||||
private def register = {
|
||||
if (currentTransaction.get.isEmpty) throw new NoTransactionInScopeException
|
||||
currentTransaction.get.get.register(uuid, this)
|
||||
if (transaction.get.isEmpty) throw new NoTransactionInScopeException
|
||||
transaction.get.get.register(uuid, this)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -272,8 +275,8 @@ trait PersistentRef[T] extends Transactional with Committable {
|
|||
}
|
||||
|
||||
private def register = {
|
||||
if (currentTransaction.get.isEmpty) throw new NoTransactionInScopeException
|
||||
currentTransaction.get.get.register(uuid, this)
|
||||
if (transaction.get.isEmpty) throw new NoTransactionInScopeException
|
||||
transaction.get.get.register(uuid, this)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -305,7 +308,7 @@ trait PersistentRef[T] extends Transactional with Committable {
|
|||
trait PersistentQueue[A] extends scala.collection.mutable.Queue[A]
|
||||
with Transactional with Committable with Logging {
|
||||
|
||||
abstract case class QueueOp
|
||||
sealed trait QueueOp
|
||||
case object ENQ extends QueueOp
|
||||
case object DEQ extends QueueOp
|
||||
|
||||
|
|
@ -397,7 +400,123 @@ trait PersistentQueue[A] extends scala.collection.mutable.Queue[A]
|
|||
throw new UnsupportedOperationException("dequeueAll not supported")
|
||||
|
||||
private def register = {
|
||||
if (currentTransaction.get.isEmpty) throw new NoTransactionInScopeException
|
||||
currentTransaction.get.get.register(uuid, this)
|
||||
if (transaction.get.isEmpty) throw new NoTransactionInScopeException
|
||||
transaction.get.get.register(uuid, this)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a template for a concrete persistent transactional sorted set based storage.
|
||||
* <p/>
|
||||
* Sorting is done based on a <i>zscore</i>. But the computation of zscore has been kept
|
||||
* outside the abstraction.
|
||||
* <p/>
|
||||
* zscore can be implemented in a variety of ways by the calling class:
|
||||
* <pre>
|
||||
* trait ZScorable {
|
||||
* def toZScore: Float
|
||||
* }
|
||||
*
|
||||
* class Foo extends ZScorable {
|
||||
* //.. implemnetation
|
||||
* }
|
||||
* </pre>
|
||||
* Or we can also use views:
|
||||
* <pre>
|
||||
* class Foo {
|
||||
* //..
|
||||
* }
|
||||
*
|
||||
* implicit def Foo2Scorable(foo: Foo): ZScorable = new ZScorable {
|
||||
* def toZScore = {
|
||||
* //..
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* and use <tt>foo.toZScore</tt> to compute the zscore and pass to the APIs.
|
||||
*
|
||||
* @author <a href="http://debasishg.blogspot.com"</a>
|
||||
*/
|
||||
trait PersistentSortedSet[A]
|
||||
extends Transactional
|
||||
with Committable {
|
||||
|
||||
protected val newElems = TransactionalState.newMap[A, Float]
|
||||
protected val removedElems = TransactionalState.newVector[A]
|
||||
|
||||
val storage: SortedSetStorageBackend[A]
|
||||
|
||||
def commit = {
|
||||
for ((element, score) <- newElems) storage.zadd(uuid, String.valueOf(score), element)
|
||||
for (element <- removedElems) storage.zrem(uuid, element)
|
||||
newElems.clear
|
||||
removedElems.clear
|
||||
}
|
||||
|
||||
def +(elem: A, score: Float) = add(elem, score)
|
||||
|
||||
def add(elem: A, score: Float) = {
|
||||
register
|
||||
newElems.put(elem, score)
|
||||
}
|
||||
|
||||
def -(elem: A) = remove(elem)
|
||||
|
||||
def remove(elem: A) = {
|
||||
register
|
||||
removedElems.add(elem)
|
||||
}
|
||||
|
||||
private def inStorage(elem: A): Option[Float] = storage.zscore(uuid, elem) match {
|
||||
case Some(s) => Some(s.toFloat)
|
||||
case None => None
|
||||
}
|
||||
|
||||
def contains(elem: A): Boolean = {
|
||||
if (newElems contains elem) true
|
||||
else {
|
||||
inStorage(elem) match {
|
||||
case Some(f) => true
|
||||
case None => false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def size: Int = newElems.size + storage.zcard(uuid) - removedElems.size
|
||||
|
||||
def zscore(elem: A): Float = {
|
||||
if (newElems contains elem) newElems.get(elem).get
|
||||
inStorage(elem) match {
|
||||
case Some(f) => f
|
||||
case None =>
|
||||
throw new Predef.NoSuchElementException(elem + " not present")
|
||||
}
|
||||
}
|
||||
|
||||
implicit def order(x: (A, Float)) = new Ordered[(A, Float)] {
|
||||
def compare(that: (A, Float)) = x._2 compare that._2
|
||||
}
|
||||
|
||||
def zrange(start: Int, end: Int): List[(A, Float)] = {
|
||||
// need to operate on the whole range
|
||||
// get all from the underlying storage
|
||||
val fromStore = storage.zrangeWithScore(uuid, 0, -1)
|
||||
val ts = scala.collection.immutable.TreeSet(fromStore: _*) ++ newElems.toList
|
||||
val l = ts.size
|
||||
|
||||
// -1 means the last element, -2 means the second last
|
||||
val s = if (start < 0) start + l else start
|
||||
val e =
|
||||
if (end < 0) end + l
|
||||
else if (end >= l) (l - 1)
|
||||
else end
|
||||
// slice is open at the end, we need a closed end range
|
||||
ts.elements.slice(s, e + 1).toList
|
||||
}
|
||||
|
||||
private def register = {
|
||||
if (transaction.get.isEmpty) throw new NoTransactionInScopeException
|
||||
transaction.get.get.register(uuid, this)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
||||
*/
|
||||
|
||||
package se.scalablesolutions.akka.state
|
||||
package se.scalablesolutions.akka.persistence.common
|
||||
|
||||
// abstracts persistence storage
|
||||
trait StorageBackend
|
||||
|
|
@ -33,6 +33,14 @@ trait VectorStorageBackend[T] extends StorageBackend {
|
|||
trait RefStorageBackend[T] extends StorageBackend {
|
||||
def insertRefStorageFor(name: String, element: T)
|
||||
def getRefStorageFor(name: String): Option[T]
|
||||
def incrementAtomically(name: String): Option[Int] =
|
||||
throw new UnsupportedOperationException // only for redis
|
||||
def incrementByAtomically(name: String, by: Int): Option[Int] =
|
||||
throw new UnsupportedOperationException // only for redis
|
||||
def decrementAtomically(name: String): Option[Int] =
|
||||
throw new UnsupportedOperationException // only for redis
|
||||
def decrementByAtomically(name: String, by: Int): Option[Int] =
|
||||
throw new UnsupportedOperationException // only for redis
|
||||
}
|
||||
|
||||
// for Queue
|
||||
|
|
@ -61,11 +69,15 @@ trait SortedSetStorageBackend[T] extends StorageBackend {
|
|||
// remove item from sorted set identified by name
|
||||
def zrem(name: String, item: T): Boolean
|
||||
|
||||
// cardinality of the set idnetified by name
|
||||
// cardinality of the set identified by name
|
||||
def zcard(name: String): Int
|
||||
|
||||
def zscore(name: String, item: T): String
|
||||
// zscore of the item from sorted set identified by name
|
||||
def zscore(name: String, item: T): Option[Float]
|
||||
|
||||
// zrange from the sorted set identified by name
|
||||
def zrange(name: String, start: Int, end: Int): List[T]
|
||||
}
|
||||
|
||||
// zrange with score from the sorted set identified by name
|
||||
def zrangeWithScore(name: String, start: Int, end: Int): List[(T, Float)]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>akka-persistence-mongo</artifactId>
|
||||
<name>Akka Persistence Mongo Module</name>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<artifactId>akka-persistence-parent</artifactId>
|
||||
<groupId>se.scalablesolutions.akka</groupId>
|
||||
<version>0.7-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<artifactId>akka-persistence-common</artifactId>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- For Mongo -->
|
||||
<dependency>
|
||||
<groupId>org.mongodb</groupId>
|
||||
<artifactId>mongo-java-driver</artifactId>
|
||||
<version>1.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
|
@ -2,16 +2,18 @@
|
|||
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
||||
*/
|
||||
|
||||
package se.scalablesolutions.akka.state
|
||||
package se.scalablesolutions.akka.persistence.mongo
|
||||
|
||||
import org.codehaus.aspectwerkz.proxy.Uuid
|
||||
import se.scalablesolutions.akka.stm._
|
||||
import se.scalablesolutions.akka.persistence.common._
|
||||
import se.scalablesolutions.akka.util.UUID
|
||||
|
||||
object MongoStorage extends Storage {
|
||||
type ElementType = AnyRef
|
||||
|
||||
def newMap: PersistentMap[ElementType, ElementType] = newMap(Uuid.newUuid.toString)
|
||||
def newVector: PersistentVector[ElementType] = newVector(Uuid.newUuid.toString)
|
||||
def newRef: PersistentRef[ElementType] = newRef(Uuid.newUuid.toString)
|
||||
def newMap: PersistentMap[ElementType, ElementType] = newMap(UUID.newUuid.toString)
|
||||
def newVector: PersistentVector[ElementType] = newVector(UUID.newUuid.toString)
|
||||
def newRef: PersistentRef[ElementType] = newRef(UUID.newUuid.toString)
|
||||
|
||||
def getMap(id: String): PersistentMap[ElementType, ElementType] = newMap(id)
|
||||
def getVector(id: String): PersistentVector[ElementType] = newVector(id)
|
||||
|
|
|
|||
|
|
@ -2,10 +2,12 @@
|
|||
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
||||
*/
|
||||
|
||||
package se.scalablesolutions.akka.state
|
||||
package se.scalablesolutions.akka.persistence.mongo
|
||||
|
||||
import se.scalablesolutions.akka.stm._
|
||||
import se.scalablesolutions.akka.persistence.common._
|
||||
import se.scalablesolutions.akka.util.Logging
|
||||
import se.scalablesolutions.akka.Config.config
|
||||
import se.scalablesolutions.akka.config.Config.config
|
||||
|
||||
import sjson.json.Serializer._
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package se.scalablesolutions.akka.state
|
||||
package se.scalablesolutions.akka.persistence.mongo
|
||||
|
||||
import junit.framework.TestCase
|
||||
|
||||
|
|
@ -8,7 +8,7 @@ import org.junit.Assert._
|
|||
import _root_.dispatch.json.{JsNumber, JsValue}
|
||||
import _root_.dispatch.json.Js._
|
||||
|
||||
import se.scalablesolutions.akka.actor.Actor
|
||||
import se.scalablesolutions.akka.actor.{Transactor, Actor}
|
||||
|
||||
/**
|
||||
* A persistent actor based on MongoDB storage.
|
||||
|
|
@ -29,10 +29,10 @@ case class MultiDebit(accountNo: String, amounts: List[BigInt], failer: Actor)
|
|||
case class Credit(accountNo: String, amount: BigInt)
|
||||
case object LogSize
|
||||
|
||||
class BankAccountActor extends Actor {
|
||||
makeTransactionRequired
|
||||
private val accountState = MongoStorage.newMap
|
||||
private val txnLog = MongoStorage.newVector
|
||||
class BankAccountActor extends Transactor {
|
||||
|
||||
private lazy val accountState = MongoStorage.newMap
|
||||
private lazy val txnLog = MongoStorage.newVector
|
||||
|
||||
def receive: PartialFunction[Any, Unit] = {
|
||||
// check balance
|
||||
|
|
@ -91,8 +91,7 @@ class BankAccountActor extends Actor {
|
|||
}
|
||||
}
|
||||
|
||||
@serializable class PersistentFailerActor extends Actor {
|
||||
makeTransactionRequired
|
||||
@serializable class PersistentFailerActor extends Transactor {
|
||||
def receive = {
|
||||
case "Failure" =>
|
||||
throw new RuntimeException("expected")
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package se.scalablesolutions.akka.state
|
||||
package se.scalablesolutions.akka.persistence.mongo
|
||||
|
||||
import junit.framework.TestCase
|
||||
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>akka-persistence-redis</artifactId>
|
||||
<name>Akka Persistence Redis Module</name>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<artifactId>akka-persistence-parent</artifactId>
|
||||
<groupId>se.scalablesolutions.akka</groupId>
|
||||
<version>0.7-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<artifactId>akka-persistence-common</artifactId>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- For Redis -->
|
||||
<dependency>
|
||||
<groupId>com.redis</groupId>
|
||||
<artifactId>redisclient</artifactId>
|
||||
<version>1.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
|
@ -2,27 +2,33 @@
|
|||
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
||||
*/
|
||||
|
||||
package se.scalablesolutions.akka.state
|
||||
package se.scalablesolutions.akka.persistence.redis
|
||||
|
||||
import org.codehaus.aspectwerkz.proxy.Uuid
|
||||
import se.scalablesolutions.akka.util.UUID
|
||||
import se.scalablesolutions.akka.stm._
|
||||
import se.scalablesolutions.akka.persistence.common._
|
||||
|
||||
object RedisStorage extends Storage {
|
||||
type ElementType = Array[Byte]
|
||||
|
||||
def newMap: PersistentMap[ElementType, ElementType] = newMap(Uuid.newUuid.toString)
|
||||
def newVector: PersistentVector[ElementType] = newVector(Uuid.newUuid.toString)
|
||||
def newRef: PersistentRef[ElementType] = newRef(Uuid.newUuid.toString)
|
||||
override def newQueue: PersistentQueue[ElementType] = newQueue(Uuid.newUuid.toString)
|
||||
def newMap: PersistentMap[ElementType, ElementType] = newMap(UUID.newUuid.toString)
|
||||
def newVector: PersistentVector[ElementType] = newVector(UUID.newUuid.toString)
|
||||
def newRef: PersistentRef[ElementType] = newRef(UUID.newUuid.toString)
|
||||
override def newQueue: PersistentQueue[ElementType] = newQueue(UUID.newUuid.toString)
|
||||
override def newSortedSet: PersistentSortedSet[ElementType] = newSortedSet(UUID.newUuid.toString)
|
||||
|
||||
def getMap(id: String): PersistentMap[ElementType, ElementType] = newMap(id)
|
||||
def getVector(id: String): PersistentVector[ElementType] = newVector(id)
|
||||
def getRef(id: String): PersistentRef[ElementType] = newRef(id)
|
||||
override def getQueue(id: String): PersistentQueue[ElementType] = newQueue(id)
|
||||
override def getSortedSet(id: String): PersistentSortedSet[ElementType] = newSortedSet(id)
|
||||
|
||||
def newMap(id: String): PersistentMap[ElementType, ElementType] = new RedisPersistentMap(id)
|
||||
def newVector(id: String): PersistentVector[ElementType] = new RedisPersistentVector(id)
|
||||
def newRef(id: String): PersistentRef[ElementType] = new RedisPersistentRef(id)
|
||||
override def newQueue(id: String): PersistentQueue[ElementType] = new RedisPersistentQueue(id)
|
||||
override def newSortedSet(id: String): PersistentSortedSet[ElementType] =
|
||||
new RedisPersistentSortedSet(id)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -61,3 +67,14 @@ class RedisPersistentQueue(id: String) extends PersistentQueue[Array[Byte]] {
|
|||
val uuid = id
|
||||
val storage = RedisStorageBackend
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a persistent transactional sorted set based on the Redis
|
||||
* storage.
|
||||
*
|
||||
* @author <a href="http://debasishg.blogspot.com">Debasish Ghosh</a>
|
||||
*/
|
||||
class RedisPersistentSortedSet(id: String) extends PersistentSortedSet[Array[Byte]] {
|
||||
val uuid = id
|
||||
val storage = RedisStorageBackend
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,12 @@
|
|||
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
||||
*/
|
||||
|
||||
package se.scalablesolutions.akka.state
|
||||
package se.scalablesolutions.akka.persistence.redis
|
||||
|
||||
import se.scalablesolutions.akka.stm._
|
||||
import se.scalablesolutions.akka.persistence.common._
|
||||
import se.scalablesolutions.akka.util.Logging
|
||||
import se.scalablesolutions.akka.Config.config
|
||||
import se.scalablesolutions.akka.config.Config.config
|
||||
|
||||
import com.redis._
|
||||
|
||||
|
|
@ -72,11 +74,11 @@ private [akka] object RedisStorageBackend extends
|
|||
* base64(T1):base64("debasish.programming_language") -> "scala"
|
||||
* </i>
|
||||
*/
|
||||
def insertMapStorageEntryFor(name: String, key: Array[Byte], value: Array[Byte]) {
|
||||
def insertMapStorageEntryFor(name: String, key: Array[Byte], value: Array[Byte]): Unit = withErrorHandling {
|
||||
insertMapStorageEntriesFor(name, List((key, value)))
|
||||
}
|
||||
|
||||
def insertMapStorageEntriesFor(name: String, entries: List[Tuple2[Array[Byte], Array[Byte]]]) {
|
||||
def insertMapStorageEntriesFor(name: String, entries: List[Tuple2[Array[Byte], Array[Byte]]]): Unit = withErrorHandling {
|
||||
mset(entries.map(e =>
|
||||
(makeRedisKey(name, e._1), new String(e._2))))
|
||||
}
|
||||
|
|
@ -89,22 +91,22 @@ private [akka] object RedisStorageBackend extends
|
|||
* <li>: is chosen since it cannot appear in base64 encoding charset</li>
|
||||
* <li>both parts of the key need to be based64 encoded since there can be spaces within each of them</li>
|
||||
*/
|
||||
private [this] def makeRedisKey(name: String, key: Array[Byte]): String = {
|
||||
private [this] def makeRedisKey(name: String, key: Array[Byte]): String = withErrorHandling {
|
||||
"%s:%s".format(new String(encode(name.getBytes)), new String(encode(key)))
|
||||
}
|
||||
|
||||
private [this] def makeKeyFromRedisKey(redisKey: String) = {
|
||||
private [this] def makeKeyFromRedisKey(redisKey: String) = withErrorHandling {
|
||||
val nk = redisKey.split(':').map{e: String => decode(e.getBytes)}
|
||||
(nk(0), nk(1))
|
||||
}
|
||||
|
||||
private [this] def mset(entries: List[(String, String)]) {
|
||||
private [this] def mset(entries: List[(String, String)]): Unit = withErrorHandling {
|
||||
entries.foreach {e: (String, String) =>
|
||||
db.set(e._1, e._2)
|
||||
}
|
||||
}
|
||||
|
||||
def removeMapStorageFor(name: String): Unit = {
|
||||
def removeMapStorageFor(name: String): Unit = withErrorHandling {
|
||||
db.keys("%s:*".format(encode(name.getBytes))) match {
|
||||
case None =>
|
||||
throw new Predef.NoSuchElementException(name + " not present")
|
||||
|
|
@ -113,18 +115,19 @@ private [akka] object RedisStorageBackend extends
|
|||
}
|
||||
}
|
||||
|
||||
def removeMapStorageFor(name: String, key: Array[Byte]): Unit = {
|
||||
def removeMapStorageFor(name: String, key: Array[Byte]): Unit = withErrorHandling {
|
||||
db.delete(makeRedisKey(name, key))
|
||||
}
|
||||
|
||||
def getMapStorageEntryFor(name: String, key: Array[Byte]): Option[Array[Byte]] =
|
||||
def getMapStorageEntryFor(name: String, key: Array[Byte]): Option[Array[Byte]] = withErrorHandling {
|
||||
db.get(makeRedisKey(name, key)) match {
|
||||
case None =>
|
||||
throw new Predef.NoSuchElementException(new String(key) + " not present")
|
||||
case Some(s) => Some(s.getBytes)
|
||||
}
|
||||
}
|
||||
|
||||
def getMapStorageSizeFor(name: String): Int = {
|
||||
def getMapStorageSizeFor(name: String): Int = withErrorHandling {
|
||||
db.keys("%s:*".format(new String(encode(name.getBytes)))) match {
|
||||
case None => 0
|
||||
case Some(keys) =>
|
||||
|
|
@ -132,7 +135,7 @@ private [akka] object RedisStorageBackend extends
|
|||
}
|
||||
}
|
||||
|
||||
def getMapStorageFor(name: String): List[(Array[Byte], Array[Byte])] = {
|
||||
def getMapStorageFor(name: String): List[(Array[Byte], Array[Byte])] = withErrorHandling {
|
||||
db.keys("%s:*".format(new String(encode(name.getBytes)))) match {
|
||||
case None =>
|
||||
throw new Predef.NoSuchElementException(name + " not present")
|
||||
|
|
@ -143,7 +146,7 @@ private [akka] object RedisStorageBackend extends
|
|||
|
||||
def getMapStorageRangeFor(name: String, start: Option[Array[Byte]],
|
||||
finish: Option[Array[Byte]],
|
||||
count: Int): List[(Array[Byte], Array[Byte])] = {
|
||||
count: Int): List[(Array[Byte], Array[Byte])] = withErrorHandling {
|
||||
|
||||
import scala.collection.immutable.TreeMap
|
||||
val wholeSorted =
|
||||
|
|
@ -188,19 +191,19 @@ private [akka] object RedisStorageBackend extends
|
|||
}
|
||||
}
|
||||
|
||||
def insertVectorStorageEntryFor(name: String, element: Array[Byte]) {
|
||||
def insertVectorStorageEntryFor(name: String, element: Array[Byte]): Unit = withErrorHandling {
|
||||
db.lpush(new String(encode(name.getBytes)), new String(element))
|
||||
}
|
||||
|
||||
def insertVectorStorageEntriesFor(name: String, elements: List[Array[Byte]]) {
|
||||
def insertVectorStorageEntriesFor(name: String, elements: List[Array[Byte]]): Unit = withErrorHandling {
|
||||
elements.foreach(insertVectorStorageEntryFor(name, _))
|
||||
}
|
||||
|
||||
def updateVectorStorageEntryFor(name: String, index: Int, elem: Array[Byte]) {
|
||||
def updateVectorStorageEntryFor(name: String, index: Int, elem: Array[Byte]): Unit = withErrorHandling {
|
||||
db.lset(new String(encode(name.getBytes)), index, new String(elem))
|
||||
}
|
||||
|
||||
def getVectorStorageEntryFor(name: String, index: Int): Array[Byte] = {
|
||||
def getVectorStorageEntryFor(name: String, index: Int): Array[Byte] = withErrorHandling {
|
||||
db.lindex(new String(encode(name.getBytes)), index) match {
|
||||
case None =>
|
||||
throw new Predef.NoSuchElementException(name + " does not have element at " + index)
|
||||
|
|
@ -208,7 +211,7 @@ private [akka] object RedisStorageBackend extends
|
|||
}
|
||||
}
|
||||
|
||||
def getVectorStorageRangeFor(name: String, start: Option[Int], finish: Option[Int], count: Int): List[Array[Byte]] = {
|
||||
def getVectorStorageRangeFor(name: String, start: Option[Int], finish: Option[Int], count: Int): List[Array[Byte]] = withErrorHandling {
|
||||
/**
|
||||
* <tt>count</tt> is the max number of results to return. Start with
|
||||
* <tt>start</tt> or 0 (if <tt>start</tt> is not defined) and go until
|
||||
|
|
@ -237,11 +240,11 @@ private [akka] object RedisStorageBackend extends
|
|||
}
|
||||
}
|
||||
|
||||
def insertRefStorageFor(name: String, element: Array[Byte]) {
|
||||
def insertRefStorageFor(name: String, element: Array[Byte]): Unit = withErrorHandling {
|
||||
db.set(new String(encode(name.getBytes)), new String(element))
|
||||
}
|
||||
|
||||
def getRefStorageFor(name: String): Option[Array[Byte]] = {
|
||||
def getRefStorageFor(name: String): Option[Array[Byte]] = withErrorHandling {
|
||||
db.get(new String(encode(name.getBytes))) match {
|
||||
case None =>
|
||||
throw new Predef.NoSuchElementException(name + " not present")
|
||||
|
|
@ -249,13 +252,46 @@ private [akka] object RedisStorageBackend extends
|
|||
}
|
||||
}
|
||||
|
||||
override def incrementAtomically(name: String): Option[Int] = withErrorHandling {
|
||||
db.incr(new String(encode(name.getBytes))) match {
|
||||
case Some(i) => Some(i)
|
||||
case None =>
|
||||
throw new Predef.IllegalArgumentException(name + " exception in incr")
|
||||
}
|
||||
}
|
||||
|
||||
override def incrementByAtomically(name: String, by: Int): Option[Int] = withErrorHandling {
|
||||
db.incrBy(new String(encode(name.getBytes)), by) match {
|
||||
case Some(i) => Some(i)
|
||||
case None =>
|
||||
throw new Predef.IllegalArgumentException(name + " exception in incrby")
|
||||
}
|
||||
}
|
||||
|
||||
override def decrementAtomically(name: String): Option[Int] = withErrorHandling {
|
||||
db.decr(new String(encode(name.getBytes))) match {
|
||||
case Some(i) => Some(i)
|
||||
case None =>
|
||||
throw new Predef.IllegalArgumentException(name + " exception in decr")
|
||||
}
|
||||
}
|
||||
|
||||
override def decrementByAtomically(name: String, by: Int): Option[Int] = withErrorHandling {
|
||||
db.decrBy(new String(encode(name.getBytes)), by) match {
|
||||
case Some(i) => Some(i)
|
||||
case None =>
|
||||
throw new Predef.IllegalArgumentException(name + " exception in decrby")
|
||||
}
|
||||
}
|
||||
|
||||
// add to the end of the queue
|
||||
def enqueue(name: String, item: Array[Byte]): Boolean = {
|
||||
def enqueue(name: String, item: Array[Byte]): Boolean = withErrorHandling {
|
||||
db.rpush(new String(encode(name.getBytes)), new String(item))
|
||||
}
|
||||
|
||||
|
||||
// pop from the front of the queue
|
||||
def dequeue(name: String): Option[Array[Byte]] = {
|
||||
def dequeue(name: String): Option[Array[Byte]] = withErrorHandling {
|
||||
db.lpop(new String(encode(name.getBytes))) match {
|
||||
case None =>
|
||||
throw new Predef.NoSuchElementException(name + " not present")
|
||||
|
|
@ -265,7 +301,7 @@ private [akka] object RedisStorageBackend extends
|
|||
}
|
||||
|
||||
// get the size of the queue
|
||||
def size(name: String): Int = {
|
||||
def size(name: String): Int = withErrorHandling {
|
||||
db.llen(new String(encode(name.getBytes))) match {
|
||||
case None =>
|
||||
throw new Predef.NoSuchElementException(name + " not present")
|
||||
|
|
@ -275,26 +311,28 @@ private [akka] object RedisStorageBackend extends
|
|||
|
||||
// return an array of items currently stored in the queue
|
||||
// start is the item to begin, count is how many items to return
|
||||
def peek(name: String, start: Int, count: Int): List[Array[Byte]] = count match {
|
||||
case 1 =>
|
||||
db.lindex(new String(encode(name.getBytes)), start) match {
|
||||
case None =>
|
||||
throw new Predef.NoSuchElementException("No element at " + start)
|
||||
case Some(s) =>
|
||||
List(s.getBytes)
|
||||
}
|
||||
case n =>
|
||||
db.lrange(new String(encode(name.getBytes)), start, start + count - 1) match {
|
||||
case None =>
|
||||
throw new Predef.NoSuchElementException(
|
||||
"No element found between " + start + " and " + (start + count - 1))
|
||||
case Some(es) =>
|
||||
es.map(_.get.getBytes)
|
||||
}
|
||||
def peek(name: String, start: Int, count: Int): List[Array[Byte]] = withErrorHandling {
|
||||
count match {
|
||||
case 1 =>
|
||||
db.lindex(new String(encode(name.getBytes)), start) match {
|
||||
case None =>
|
||||
throw new Predef.NoSuchElementException("No element at " + start)
|
||||
case Some(s) =>
|
||||
List(s.getBytes)
|
||||
}
|
||||
case n =>
|
||||
db.lrange(new String(encode(name.getBytes)), start, start + count - 1) match {
|
||||
case None =>
|
||||
throw new Predef.NoSuchElementException(
|
||||
"No element found between " + start + " and " + (start + count - 1))
|
||||
case Some(es) =>
|
||||
es.map(_.get.getBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// completely delete the queue
|
||||
def remove(name: String): Boolean = {
|
||||
def remove(name: String): Boolean = withErrorHandling {
|
||||
db.delete(new String(encode(name.getBytes))) match {
|
||||
case Some(1) => true
|
||||
case _ => false
|
||||
|
|
@ -302,7 +340,7 @@ private [akka] object RedisStorageBackend extends
|
|||
}
|
||||
|
||||
// add item to sorted set identified by name
|
||||
def zadd(name: String, zscore: String, item: Array[Byte]): Boolean = {
|
||||
def zadd(name: String, zscore: String, item: Array[Byte]): Boolean = withErrorHandling {
|
||||
db.zadd(new String(encode(name.getBytes)), zscore, new String(item)) match {
|
||||
case Some(1) => true
|
||||
case _ => false
|
||||
|
|
@ -310,7 +348,7 @@ private [akka] object RedisStorageBackend extends
|
|||
}
|
||||
|
||||
// remove item from sorted set identified by name
|
||||
def zrem(name: String, item: Array[Byte]): Boolean = {
|
||||
def zrem(name: String, item: Array[Byte]): Boolean = withErrorHandling {
|
||||
db.zrem(new String(encode(name.getBytes)), new String(item)) match {
|
||||
case Some(1) => true
|
||||
case _ => false
|
||||
|
|
@ -318,7 +356,7 @@ private [akka] object RedisStorageBackend extends
|
|||
}
|
||||
|
||||
// cardinality of the set identified by name
|
||||
def zcard(name: String): Int = {
|
||||
def zcard(name: String): Int = withErrorHandling {
|
||||
db.zcard(new String(encode(name.getBytes))) match {
|
||||
case None =>
|
||||
throw new Predef.NoSuchElementException(name + " not present")
|
||||
|
|
@ -326,15 +364,14 @@ private [akka] object RedisStorageBackend extends
|
|||
}
|
||||
}
|
||||
|
||||
def zscore(name: String, item: Array[Byte]): String = {
|
||||
def zscore(name: String, item: Array[Byte]): Option[Float] = withErrorHandling {
|
||||
db.zscore(new String(encode(name.getBytes)), new String(item)) match {
|
||||
case None =>
|
||||
throw new Predef.NoSuchElementException(new String(item) + " not present")
|
||||
case Some(s) => s
|
||||
case Some(s) => Some(s.toFloat)
|
||||
case None => None
|
||||
}
|
||||
}
|
||||
|
||||
def zrange(name: String, start: Int, end: Int): List[Array[Byte]] = {
|
||||
def zrange(name: String, start: Int, end: Int): List[Array[Byte]] = withErrorHandling {
|
||||
db.zrange(new String(encode(name.getBytes)), start.toString, end.toString, RedisClient.ASC, false) match {
|
||||
case None =>
|
||||
throw new Predef.NoSuchElementException(name + " not present")
|
||||
|
|
@ -342,6 +379,27 @@ private [akka] object RedisStorageBackend extends
|
|||
s.map(_.get.getBytes)
|
||||
}
|
||||
}
|
||||
|
||||
def zrangeWithScore(name: String, start: Int, end: Int): List[(Array[Byte], Float)] = withErrorHandling {
|
||||
db.zrangeWithScore(
|
||||
new String(encode(name.getBytes)), start.toString, end.toString, RedisClient.ASC) match {
|
||||
case None =>
|
||||
throw new Predef.NoSuchElementException(name + " not present")
|
||||
case Some(l) =>
|
||||
l.map{ case (elem, score) => (elem.get.getBytes, score.get.toFloat) }
|
||||
}
|
||||
}
|
||||
|
||||
def flushDB = db.flushDb
|
||||
def flushDB = withErrorHandling(db.flushDb)
|
||||
|
||||
private def withErrorHandling[T](body: => T): T = {
|
||||
try {
|
||||
body
|
||||
} catch {
|
||||
case e: java.lang.NullPointerException =>
|
||||
throw new StorageException("Could not connect to Redis server")
|
||||
case e =>
|
||||
throw new StorageException("Error in Redis: " + e.getMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package se.scalablesolutions.akka.state
|
||||
package se.scalablesolutions.akka.persistence.redis
|
||||
|
||||
import junit.framework.TestCase
|
||||
|
||||
|
|
@ -29,6 +29,7 @@ case object LogSize
|
|||
class AccountActor extends Transactor {
|
||||
private lazy val accountState = RedisStorage.newMap
|
||||
private lazy val txnLog = RedisStorage.newVector
|
||||
//timeout = 5000
|
||||
|
||||
def receive = {
|
||||
// check balance
|
||||
|
|
@ -86,6 +87,7 @@ class AccountActor extends Transactor {
|
|||
}
|
||||
|
||||
@serializable class PersistentFailerActor extends Transactor {
|
||||
// timeout = 5000
|
||||
def receive = {
|
||||
case "Failure" =>
|
||||
throw new RuntimeException("expected")
|
||||
|
|
@ -138,7 +140,7 @@ class RedisPersistentActorSpec extends TestCase {
|
|||
bactor.start
|
||||
bactor !! Credit("a-123", 5000)
|
||||
|
||||
assertEquals(BigInt(5000), (bactor !! Balance("a-123")).get)
|
||||
assertEquals(BigInt(5000), (bactor !! (Balance("a-123"), 5000)).get)
|
||||
|
||||
val failer = new PersistentFailerActor
|
||||
failer.start
|
||||
|
|
@ -147,7 +149,7 @@ class RedisPersistentActorSpec extends TestCase {
|
|||
fail("should throw exception")
|
||||
} catch { case e: RuntimeException => {}}
|
||||
|
||||
assertEquals(BigInt(5000), (bactor !! Balance("a-123")).get)
|
||||
assertEquals(BigInt(5000), (bactor !! (Balance("a-123"), 5000)).get)
|
||||
|
||||
// should not count the failed one
|
||||
assertEquals(3, (bactor !! LogSize).get)
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue