Merge with master

This commit is contained in:
Viktor Klang 2010-08-03 11:40:25 +02:00
commit 98d3034a9a
271 changed files with 9230 additions and 4508 deletions

5
.gitignore vendored
View file

@ -9,6 +9,7 @@ project/boot/*
lib_managed
etags
TAGS
akka.tmproj
reports
dist
build
@ -32,9 +33,11 @@ tm.out
*.iws
*.ipr
*.iml
run-codefellow
.project
.settings
.classpath
.idea
.scala_dependencies
multiverse.log
multiverse.log
.eprj

View file

@ -3,7 +3,7 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<name>Akka Active Object Tests in Java</name>
<name>Akka TypedActor Tests in Java</name>
<artifactId>akka-active-object-test</artifactId>
<groupId>se.scalablesolutions.akka</groupId>
<version>0.9</version>

View file

@ -10,7 +10,7 @@ public class AllTest extends TestCase {
suite.addTestSuite(InMemoryStateTest.class);
suite.addTestSuite(InMemNestedStateTest.class);
suite.addTestSuite(RemoteInMemoryStateTest.class);
suite.addTestSuite(ActiveObjectGuiceConfiguratorTest.class);
suite.addTestSuite(TypedActorGuiceConfiguratorTest.class);
return suite;
}

View file

@ -6,7 +6,7 @@ package se.scalablesolutions.akka.api;
import se.scalablesolutions.akka.config.*;
import se.scalablesolutions.akka.config.Config;
import se.scalablesolutions.akka.config.ActiveObjectConfigurator;
import se.scalablesolutions.akka.config.TypedActorConfigurator;
import static se.scalablesolutions.akka.config.JavaConfig.*;
import se.scalablesolutions.akka.actor.*;
import junit.framework.TestCase;
@ -14,7 +14,7 @@ import junit.framework.TestCase;
public class InMemNestedStateTest extends TestCase {
static String messageLog = "";
final private ActiveObjectConfigurator conf = new ActiveObjectConfigurator();
final private TypedActorConfigurator conf = new TypedActorConfigurator();
public InMemNestedStateTest() {
conf.configure(

View file

@ -8,7 +8,7 @@ import junit.framework.TestCase;
import se.scalablesolutions.akka.config.Config;
import se.scalablesolutions.akka.config.*;
import se.scalablesolutions.akka.config.ActiveObjectConfigurator;
import se.scalablesolutions.akka.config.TypedActorConfigurator;
import static se.scalablesolutions.akka.config.JavaConfig.*;
@ -17,7 +17,7 @@ import se.scalablesolutions.akka.actor.*;
public class InMemoryStateTest extends TestCase {
static String messageLog = "";
final private ActiveObjectConfigurator conf = new ActiveObjectConfigurator();
final private TypedActorConfigurator conf = new TypedActorConfigurator();
public InMemoryStateTest() {
Config.config();

View file

@ -1,7 +1,7 @@
package se.scalablesolutions.akka.api;
import static se.scalablesolutions.akka.actor.ActiveObject.link;
import static se.scalablesolutions.akka.actor.ActiveObject.newInstance;
import static se.scalablesolutions.akka.actor.TypedActor.link;
import static se.scalablesolutions.akka.actor.TypedActor.newInstance;
import org.junit.Assert;
import org.junit.Test;
@ -15,7 +15,7 @@ import junit.framework.TestCase;
* @author johanrask
*
*/
public class MiscActiveObjectTest extends TestCase {
public class MiscTypedActorTest extends TestCase {
/**

View file

@ -5,8 +5,8 @@
package se.scalablesolutions.akka.api;
import se.scalablesolutions.akka.config.Config;
import se.scalablesolutions.akka.actor.ActiveObject;
import se.scalablesolutions.akka.config.ActiveObjectConfigurator;
import se.scalablesolutions.akka.actor.TypedActor;
import se.scalablesolutions.akka.config.TypedActorConfigurator;
import se.scalablesolutions.akka.remote.RemoteNode;
import junit.framework.TestCase;
@ -23,14 +23,14 @@ public class RemoteInMemoryStateTest extends TestCase {
try { Thread.currentThread().sleep(1000); } catch (Exception e) {}
Config.config();
}
final ActiveObjectConfigurator conf = new ActiveObjectConfigurator();
final TypedActorConfigurator conf = new TypedActorConfigurator();
protected void tearDown() {
conf.stop();
}
public void testMapShouldNotRollbackStateForStatefulServerInCaseOfSuccess() {
InMemStateful stateful = ActiveObject.newRemoteInstance(InMemStateful.class, 1000, "localhost", 9999);
InMemStateful stateful = TypedActor.newRemoteInstance(InMemStateful.class, 1000, "localhost", 9999);
stateful.init();
stateful.setMapState("testShouldNotRollbackStateForStatefulServerInCaseOfSuccess", "init"); // set init state
stateful.success("testShouldNotRollbackStateForStatefulServerInCaseOfSuccess", "new state"); // transactionrequired
@ -38,10 +38,10 @@ public class RemoteInMemoryStateTest extends TestCase {
}
public void testMapShouldRollbackStateForStatefulServerInCaseOfFailure() {
InMemStateful stateful = ActiveObject.newRemoteInstance(InMemStateful.class, 10000, "localhost", 9999);
InMemStateful stateful = TypedActor.newRemoteInstance(InMemStateful.class, 10000, "localhost", 9999);
stateful.init();
stateful.setMapState("testShouldRollbackStateForStatefulServerInCaseOfFailure", "init"); // set init state
InMemFailer failer = ActiveObject.newRemoteInstance(InMemFailer.class, 1000, "localhost", 9999); //conf.getInstance(InMemFailer.class);
InMemFailer failer = TypedActor.newRemoteInstance(InMemFailer.class, 1000, "localhost", 9999); //conf.getInstance(InMemFailer.class);
try {
stateful.failure("testShouldRollbackStateForStatefulServerInCaseOfFailure", "new state", failer); // call failing transactionrequired method
fail("should have thrown an exception");
@ -51,7 +51,7 @@ public class RemoteInMemoryStateTest extends TestCase {
}
public void testVectorShouldNotRollbackStateForStatefulServerInCaseOfSuccess() {
InMemStateful stateful = ActiveObject.newRemoteInstance(InMemStateful.class, 10000, "localhost", 9999);
InMemStateful stateful = TypedActor.newRemoteInstance(InMemStateful.class, 10000, "localhost", 9999);
stateful.init();
stateful.setVectorState("init"); // set init state
stateful.success("testShouldNotRollbackStateForStatefulServerInCaseOfSuccess", "new state"); // transactionrequired
@ -59,10 +59,10 @@ public class RemoteInMemoryStateTest extends TestCase {
}
public void testVectorShouldRollbackStateForStatefulServerInCaseOfFailure() {
InMemStateful stateful = ActiveObject.newRemoteInstance(InMemStateful.class, 10000, "localhost", 9999);
InMemStateful stateful = TypedActor.newRemoteInstance(InMemStateful.class, 10000, "localhost", 9999);
stateful.init();
stateful.setVectorState("init"); // set init state
InMemFailer failer = ActiveObject.newRemoteInstance(InMemFailer.class, 10000, "localhost", 9999); //conf.getInstance(InMemFailer.class);
InMemFailer failer = TypedActor.newRemoteInstance(InMemFailer.class, 10000, "localhost", 9999); //conf.getInstance(InMemFailer.class);
try {
stateful.failure("testShouldRollbackStateForStatefulServerInCaseOfFailure", "new state", failer); // call failing transactionrequired method
fail("should have thrown an exception");
@ -72,7 +72,7 @@ public class RemoteInMemoryStateTest extends TestCase {
}
public void testRefShouldNotRollbackStateForStatefulServerInCaseOfSuccess() {
InMemStateful stateful = ActiveObject.newRemoteInstance(InMemStateful.class, 10000, "localhost", 9999);
InMemStateful stateful = TypedActor.newRemoteInstance(InMemStateful.class, 10000, "localhost", 9999);
stateful.init();
stateful.setRefState("init"); // set init state
stateful.success("testShouldNotRollbackStateForStatefulServerInCaseOfSuccess", "new state"); // transactionrequired
@ -80,10 +80,10 @@ public class RemoteInMemoryStateTest extends TestCase {
}
public void testRefShouldRollbackStateForStatefulServerInCaseOfFailure() {
InMemStateful stateful = ActiveObject.newRemoteInstance(InMemStateful.class, 10000, "localhost", 9999);
InMemStateful stateful = TypedActor.newRemoteInstance(InMemStateful.class, 10000, "localhost", 9999);
stateful.init();
stateful.setRefState("init"); // set init state
InMemFailer failer = ActiveObject.newRemoteInstance(InMemFailer.class, 10000, "localhost", 9999); //conf.getInstance(InMemFailer.class);
InMemFailer failer = TypedActor.newRemoteInstance(InMemFailer.class, 10000, "localhost", 9999); //conf.getInstance(InMemFailer.class);
try {
stateful.failure("testShouldRollbackStateForStatefulServerInCaseOfFailure", "new state", failer); // call failing transactionrequired method
fail("should have thrown an exception");

View file

@ -10,14 +10,14 @@ import com.google.inject.Scopes;
import junit.framework.TestCase;
import se.scalablesolutions.akka.config.Config;
import se.scalablesolutions.akka.config.ActiveObjectConfigurator;
import se.scalablesolutions.akka.config.TypedActorConfigurator;
import static se.scalablesolutions.akka.config.JavaConfig.*;
import se.scalablesolutions.akka.dispatch.*;
public class ActiveObjectGuiceConfiguratorTest extends TestCase {
public class TypedActorGuiceConfiguratorTest extends TestCase {
static String messageLog = "";
final private ActiveObjectConfigurator conf = new ActiveObjectConfigurator();
final private TypedActorConfigurator conf = new TypedActorConfigurator();
protected void setUp() {
Config.config();
@ -46,7 +46,7 @@ public class ActiveObjectGuiceConfiguratorTest extends TestCase {
}
public void testGuiceActiveObjectInjection() {
public void testGuiceTypedActorInjection() {
messageLog = "";
Foo foo = conf.getInstance(Foo.class);
Bar bar = conf.getInstance(Bar.class);
@ -69,7 +69,7 @@ public class ActiveObjectGuiceConfiguratorTest extends TestCase {
}
}
public void testActiveObjectInvocation() throws InterruptedException {
public void testTypedActorInvocation() throws InterruptedException {
messageLog = "";
Foo foo = conf.getInstance(Foo.class);
messageLog += foo.foo("foo ");
@ -79,7 +79,7 @@ public class ActiveObjectGuiceConfiguratorTest extends TestCase {
assertEquals("foo return_foo before_bar ", messageLog);
}
public void testActiveObjectInvocationsInvocation() throws InterruptedException {
public void testTypedActorInvocationsInvocation() throws InterruptedException {
messageLog = "";
Foo foo = conf.getInstance(Foo.class);
Bar bar = conf.getInstance(Bar.class);

View file

@ -136,8 +136,8 @@ object AMQP {
def toBinary(t: T): Array[Byte]
}
case class RpcClientSerializer[O,I](toBinary: ToBinary[O], fromBinary: FromBinary[I])
case class RpcServerSerializer[I,O](fromBinary: FromBinary[I], toBinary: ToBinary[O])
}

View file

@ -14,7 +14,7 @@ import java.lang.Throwable
private[amqp] class ConsumerActor(consumerParameters: ConsumerParameters)
extends FaultTolerantChannelActor(consumerParameters.exchangeParameters, consumerParameters.channelParameters) {
import consumerParameters._
import exchangeParameters._

View file

@ -14,7 +14,7 @@ import se.scalablesolutions.akka.amqp.AMQP.{ExchangeParameters, ChannelParameter
abstract private[amqp] class FaultTolerantChannelActor(
exchangeParameters: ExchangeParameters, channelParameters: Option[ChannelParameters]) extends Actor {
import exchangeParameters._
protected[amqp] var channel: Option[Channel] = None
@ -104,4 +104,4 @@ abstract private[amqp] class FaultTolerantChannelActor(
}
override def shutdown = closeChannel
}
}

View file

@ -9,7 +9,7 @@ import se.scalablesolutions.akka.amqp.AMQP.ProducerParameters
private[amqp] class ProducerActor(producerParameters: ProducerParameters)
extends FaultTolerantChannelActor(producerParameters.exchangeParameters, producerParameters.channelParameters) {
import producerParameters._
import exchangeParameters._

View file

@ -4,6 +4,9 @@
package se.scalablesolutions.akka.amqp
import se.scalablesolutions.akka.serialization.Serializer
import se.scalablesolutions.akka.amqp.AMQP.{ChannelParameters, ExchangeParameters}
import com.rabbitmq.client.{Channel, RpcClient}
import se.scalablesolutions.akka.amqp.AMQP.{RpcClientSerializer, ChannelParameters, ExchangeParameters}
@ -20,7 +23,6 @@ class RpcClientActor[I,O](exchangeParameters: ExchangeParameters,
def specificMessageHandler = {
case payload: I => {
rpcClient match {
case Some(client) =>
val response: Array[Byte] = client.primitiveCall(serializer.toBinary.toBinary(payload))
@ -30,18 +32,12 @@ class RpcClientActor[I,O](exchangeParameters: ExchangeParameters,
}
}
protected def setupChannel(ch: Channel) = {
rpcClient = Some(new RpcClient(ch, exchangeName, routingKey))
}
protected def setupChannel(ch: Channel) = rpcClient = Some(new RpcClient(ch, exchangeName, routingKey))
override def preRestart(reason: Throwable) = {
rpcClient = None
super.preRestart(reason)
}
override def toString(): String =
"AMQP.RpcClient[exchange=" +exchangeName +
", routingKey=" + routingKey+ "]"
}
override def toString = "AMQP.RpcClient[exchange=" +exchangeName + ", routingKey=" + routingKey+ "]"
}

View file

@ -31,4 +31,4 @@ class RpcServerActor[I,O](producer: ActorRef, serializer: RpcServerSerializer[I,
override def toString(): String =
"AMQP.RpcServer[]"
}
}

View file

@ -56,4 +56,4 @@ class AMQPConnectionRecoveryTest extends JUnitSuite with MustMatchers with Loggi
// this dummy test makes sure that the whole test class doesn't fail because of missing tests
assert(true)
}
}
}

View file

@ -67,4 +67,4 @@ class AMQPConsumerChannelRecoveryTest extends JUnitSuite with MustMatchers with
// this dummy test makes sure that the whole test class doesn't fail because of missing tests
assert(true)
}
}
}

View file

@ -86,4 +86,4 @@ class AMQPConsumerConnectionRecoveryTest extends JUnitSuite with MustMatchers wi
// this dummy test makes sure that the whole test class doesn't fail because of missing tests
assert(true)
}
}
}

View file

@ -64,4 +64,4 @@ class AMQPConsumerManualAcknowledgeTest extends JUnitSuite with MustMatchers wit
// this dummy test makes sure that the whole test class doesn't fail because of missing tests
assert(true)
}
}
}

View file

@ -38,7 +38,7 @@ class AMQPConsumerMessageTest extends JUnitSuite with MustMatchers with Logging
val producer = AMQP.newProducer(connection,
ProducerParameters(exchangeParameters, channelParameters = Some(channelParameters)))
countDown.await(2, TimeUnit.SECONDS) must be (true)
producer ! Message("some_payload".getBytes, "non.interesting.routing.key")
payloadLatch.tryAwait(2, TimeUnit.SECONDS) must be (true)
@ -53,4 +53,4 @@ class AMQPConsumerMessageTest extends JUnitSuite with MustMatchers with Logging
// this dummy test makes sure that the whole test class doesn't fail because of missing tests
assert(true)
}
}
}

View file

@ -60,4 +60,4 @@ class AMQPProducerChannelRecoveryTest extends JUnitSuite with MustMatchers with
// this dummy test makes sure that the whole test class doesn't fail because of missing tests
assert(true)
}
}
}

View file

@ -59,4 +59,4 @@ class AMQPProducerConnectionRecoveryTest extends JUnitSuite with MustMatchers wi
// this dummy test makes sure that the whole test class doesn't fail because of missing tests
assert(true)
}
}
}

View file

@ -21,7 +21,7 @@ class AMQPProducerMessageTest extends JUnitSuite with MustMatchers with Logging
@Test
def producerMessage = if (AMQPTest.enabled) {
val connection: ActorRef = AMQP.newConnection()
try {
val returnLatch = new StandardLatch
@ -48,4 +48,4 @@ class AMQPProducerMessageTest extends JUnitSuite with MustMatchers with Logging
// this dummy test makes sure that the whole test class doesn't fail because of missing tests
assert(true)
}
}
}

View file

@ -68,4 +68,4 @@ class AMQPRpcClientServerTest extends JUnitSuite with MustMatchers with Logging
// this dummy test makes sure that the whole test class doesn't fail because of missing tests
assert(true)
}
}
}

View file

@ -6,4 +6,4 @@ package se.scalablesolutions.akka.amqp.test
object AMQPTest {
def enabled = false
}
}

View file

@ -10,7 +10,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Target({ElementType.METHOD})
public @interface consume {
public abstract String value();

View file

@ -1 +0,0 @@
class=se.scalablesolutions.akka.camel.component.ActiveObjectComponent

View file

@ -0,0 +1 @@
class=se.scalablesolutions.akka.camel.component.TypedActorComponent

View file

@ -9,7 +9,7 @@ import java.util.Map
import org.apache.camel.{ProducerTemplate, CamelContext}
import org.apache.camel.impl.DefaultCamelContext
import se.scalablesolutions.akka.camel.component.ActiveObjectComponent
import se.scalablesolutions.akka.camel.component.TypedActorComponent
import se.scalablesolutions.akka.util.Logging
/**
@ -29,15 +29,15 @@ trait CamelContextLifecycle extends Logging {
private var _started = false
/**
* Camel component for accessing active objects.
* Camel component for accessing typed actors.
*/
private[camel] var activeObjectComponent: ActiveObjectComponent = _
private[camel] var typedActorComponent: TypedActorComponent = _
/**
* Registry in which active objects are TEMPORARILY registered during
* creation of Camel routes to active objects.
* Registry in which typed actors are TEMPORARILY registered during
* creation of Camel routes to typed actors.
*/
private[camel] var activeObjectRegistry: Map[String, AnyRef] = _
private[camel] var typedActorRegistry: Map[String, AnyRef] = _
/**
* Returns the managed CamelContext.
@ -93,15 +93,15 @@ trait CamelContextLifecycle extends Logging {
* CamelContext stream-caching is enabled. If applications want to disable stream-
* caching they can do so after this method returned and prior to calling start.
* This method also registers a new
* {@link se.scalablesolutions.akka.camel.component.ActiveObjectComponent} at
* <code>context</code> under a name defined by ActiveObjectComponent.InternalSchema.
* {@link se.scalablesolutions.akka.camel.component.TypedActorComponent} at
* <code>context</code> under a name defined by TypedActorComponent.InternalSchema.
*/
def init(context: CamelContext) {
this.activeObjectComponent = new ActiveObjectComponent
this.activeObjectRegistry = activeObjectComponent.activeObjectRegistry
this.typedActorComponent = new TypedActorComponent
this.typedActorRegistry = typedActorComponent.typedActorRegistry
this.context = context
this.context.setStreamCaching(true)
this.context.addComponent(ActiveObjectComponent.InternalSchema, activeObjectComponent)
this.context.addComponent(TypedActorComponent.InternalSchema, typedActorComponent)
this.template = context.createProducerTemplate
_initialized = true
log.info("Camel context initialized")

View file

@ -1,15 +1,18 @@
/**
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
*/
package se.scalablesolutions.akka.camel
import java.util.concurrent.CountDownLatch
import org.apache.camel.CamelContext
import se.scalablesolutions.akka.actor.Actor._
import se.scalablesolutions.akka.actor.{AspectInitRegistry, ActorRegistry}
import se.scalablesolutions.akka.util.{Bootable, Logging}
/**
* Used by applications (and the Kernel) to publish consumer actors and active objects via
* Used by applications (and the Kernel) to publish consumer actors and typed actors via
* Camel endpoints and to manage the life cycle of a a global CamelContext which can be
* accessed via <code>se.scalablesolutions.akka.camel.CamelContextManager.context</code>.
*
@ -32,8 +35,8 @@ trait CamelService extends Bootable with Logging {
* 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. A started (loaded) CamelService
* also publishes <code>@consume</code> annotated methods of active objects that have been created
* with <code>ActiveObject.newInstance(..)</code> (and <code>ActiveObject.newInstance(..)</code>
* also publishes <code>@consume</code> annotated methods of typed actors that have been created
* with <code>TypedActor.newInstance(..)</code> (and <code>TypedActor.newInstance(..)</code>
* on a remote node).
*/
abstract override def onLoad = {
@ -43,7 +46,7 @@ trait CamelService extends Bootable with Logging {
if (!initialized) init
if (!started) start
// start actor that exposes consumer actors and active objects via Camel endpoints
// start actor that exposes consumer actors and typed actors via Camel endpoints
consumerPublisher.start
// init publishRequestor so that buffered and future events are delivered to consumerPublisher
@ -77,27 +80,53 @@ trait CamelService extends Bootable with Logging {
* @see onUnload
*/
def unload = onUnload
/**
* Sets an expectation of the number of upcoming endpoint activations and returns
* a {@link CountDownLatch} that can be used to wait for the activations to occur.
* Endpoint activations that occurred in the past are not considered.
*/
def expectEndpointActivationCount(count: Int): CountDownLatch =
(consumerPublisher !! SetExpectedRegistrationCount(count)).as[CountDownLatch].get
/**
* Sets an expectation of the number of upcoming endpoint de-activations and returns
* a {@link CountDownLatch} that can be used to wait for the de-activations to occur.
* Endpoint de-activations that occurred in the past are not considered.
*/
def expectEndpointDeactivationCount(count: Int): CountDownLatch =
(consumerPublisher !! SetExpectedUnregistrationCount(count)).as[CountDownLatch].get
}
/**
* CamelService companion object used by standalone applications to create their own
* CamelService instance.
* Single CamelService instance.
*
* @author Martin Krasser
*/
object CamelService {
object CamelService extends CamelService {
/**
* Creates a new CamelService instance.
* Starts the CamelService singleton.
*/
def newInstance: CamelService = new DefaultCamelService
def start = load
/**
* Stops the CamelService singleton.
*/
def stop = unload
}
/**
* Default CamelService implementation to be created in Java applications with
* <pre>
* CamelService service = new DefaultCamelService()
* </pre>
*/
class DefaultCamelService extends CamelService {
object CamelServiceFactory {
/**
* Creates a new CamelService instance
*/
def createCamelService: CamelService = new CamelService { }
/**
* Creates a new CamelService instance
*/
def createCamelService(camelContext: CamelContext): CamelService = {
CamelContextManager.init(camelContext)
createCamelService
}
}

View file

@ -4,7 +4,7 @@
package se.scalablesolutions.akka.camel
import se.scalablesolutions.akka.actor.Actor
import se.scalablesolutions.akka.actor.{ActorRef, Actor}
/**
* Mixed in by Actor implementations that consume message from Camel endpoints.
@ -12,9 +12,34 @@ import se.scalablesolutions.akka.actor.Actor
* @author Martin Krasser
*/
trait Consumer { self: Actor =>
/**
* Returns the Camel endpoint URI to consume messages from.
*/
def endpointUri: String
/**
* Determines whether two-way communications with this consumer actor should
* be done in blocking or non-blocking mode (default is non-blocking). One-way
* communications never block.
*/
def blocking = false
}
/**
* @author Martin Krasser
*/
private[camel] object Consumer {
/**
* Applies a function <code>f</code> to <code>actorRef</code> if <code>actorRef</code>
* references a consumer actor. A valid reference to a consumer actor is a local actor
* reference with a target actor that implements the <code>Consumer</code> trait. The
* target <code>Consumer</code> object is passed as argument to <code>f</code>. This
* method returns <code>None</code> if <code>actorRef</code> is not a valid reference
* to a consumer actor, <code>Some</code> result otherwise.
*/
def forConsumer[T](actorRef: ActorRef)(f: Consumer => T): Option[T] = {
if (!actorRef.actor.isInstanceOf[Consumer]) None
else if (actorRef.remoteAddress.isDefined) None
else Some(f(actorRef.actor.asInstanceOf[Consumer]))
}
}

View file

@ -13,7 +13,7 @@ import org.apache.camel.builder.RouteBuilder
import se.scalablesolutions.akka.actor._
import se.scalablesolutions.akka.actor.annotation.consume
import se.scalablesolutions.akka.camel.component.ActiveObjectComponent
import se.scalablesolutions.akka.camel.component.TypedActorComponent
import se.scalablesolutions.akka.util.Logging
/**
@ -24,7 +24,7 @@ private[camel] object ConsumerPublisher extends Logging {
* Creates a route to the registered consumer actor.
*/
def handleConsumerRegistered(event: ConsumerRegistered) {
CamelContextManager.context.addRoutes(new ConsumerActorRoute(event.uri, event.id, event.uuid))
CamelContextManager.context.addRoutes(new ConsumerActorRoute(event.uri, event.uuid, event.blocking))
log.info("published actor %s at endpoint %s" format (event.actorRef, event.uri))
}
@ -32,20 +32,20 @@ private[camel] object ConsumerPublisher extends Logging {
* Stops route to the already un-registered consumer actor.
*/
def handleConsumerUnregistered(event: ConsumerUnregistered) {
CamelContextManager.context.stopRoute(event.id)
CamelContextManager.context.stopRoute(event.uuid)
log.info("unpublished actor %s from endpoint %s" format (event.actorRef, event.uri))
}
/**
* Creates a route to an active object method.
* Creates a route to an typed actor method.
*/
def handleConsumerMethodRegistered(event: ConsumerMethodRegistered) {
val targetMethod = event.method.getName
val objectId = "%s_%s" format (event.init.actorRef.uuid, targetMethod)
CamelContextManager.activeObjectRegistry.put(objectId, event.activeObject)
CamelContextManager.typedActorRegistry.put(objectId, event.typedActor)
CamelContextManager.context.addRoutes(new ConsumerMethodRoute(event.uri, objectId, targetMethod))
log.info("published method %s of %s at endpoint %s" format (targetMethod, event.activeObject, event.uri))
log.info("published method %s of %s at endpoint %s" format (targetMethod, event.typedActor, event.uri))
}
/**
@ -55,66 +55,66 @@ private[camel] object ConsumerPublisher extends Logging {
val targetMethod = event.method.getName
val objectId = "%s_%s" format (event.init.actorRef.uuid, targetMethod)
CamelContextManager.activeObjectRegistry.remove(objectId)
CamelContextManager.typedActorRegistry.remove(objectId)
CamelContextManager.context.stopRoute(objectId)
log.info("unpublished method %s of %s from endpoint %s" format (targetMethod, event.activeObject, event.uri))
log.info("unpublished method %s of %s from endpoint %s" format (targetMethod, event.typedActor, event.uri))
}
}
/**
* Actor that publishes consumer actors and active object methods at Camel endpoints.
* Actor that publishes consumer actors and typed actor methods at Camel endpoints.
* The Camel context used for publishing is CamelContextManager.context. This actor
* accepts messages of type
* se.scalablesolutions.akka.camel.service.ConsumerRegistered,
* se.scalablesolutions.akka.camel.service.ConsumerMethodRegistered and
* se.scalablesolutions.akka.camel.service.ConsumerUnregistered.
* se.scalablesolutions.akka.camel.ConsumerRegistered,
* se.scalablesolutions.akka.camel.ConsumerUnregistered.
* se.scalablesolutions.akka.camel.ConsumerMethodRegistered and
* se.scalablesolutions.akka.camel.ConsumerMethodUnregistered.
*
* @author Martin Krasser
*/
private[camel] class ConsumerPublisher extends Actor {
import ConsumerPublisher._
@volatile private var latch = new CountDownLatch(0)
@volatile private var registrationLatch = new CountDownLatch(0)
@volatile private var unregistrationLatch = new CountDownLatch(0)
/**
* Adds a route to the actor identified by a Publish message to the global CamelContext.
*/
protected def receive = {
case r: ConsumerRegistered => {
handleConsumerRegistered(r)
latch.countDown // needed for testing only.
registrationLatch.countDown
}
case u: ConsumerUnregistered => {
handleConsumerUnregistered(u)
latch.countDown // needed for testing only.
unregistrationLatch.countDown
}
case mr: ConsumerMethodRegistered => {
handleConsumerMethodRegistered(mr)
latch.countDown // needed for testing only.
registrationLatch.countDown
}
case mu: ConsumerMethodUnregistered => {
handleConsumerMethodUnregistered(mu)
latch.countDown // needed for testing only.
unregistrationLatch.countDown
}
case SetExpectedMessageCount(num) => {
// needed for testing only.
latch = new CountDownLatch(num)
self.reply(latch)
case SetExpectedRegistrationCount(num) => {
registrationLatch = new CountDownLatch(num)
self.reply(registrationLatch)
}
case SetExpectedUnregistrationCount(num) => {
unregistrationLatch = new CountDownLatch(num)
self.reply(unregistrationLatch)
}
case _ => { /* ignore */}
}
}
/**
* Command message used For testing-purposes only.
*/
private[camel] case class SetExpectedMessageCount(num: Int)
private[camel] case class SetExpectedRegistrationCount(num: Int)
private[camel] case class SetExpectedUnregistrationCount(num: Int)
/**
* Defines an abstract route to a target which is either an actor or an active object method..
* Defines an abstract route to a target which is either an actor or an typed actor method..
*
* @param endpointUri endpoint URI of the consumer actor or active object method.
* @param id actor identifier or active object identifier (registry key).
* @param endpointUri endpoint URI of the consumer actor or typed actor method.
* @param id actor identifier or typed actor identifier (registry key).
*
* @author Martin Krasser
*/
@ -139,31 +139,30 @@ private[camel] abstract class ConsumerRoute(endpointUri: String, id: String) ext
* 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 Actor.getId.
* @param uuid actor uuid
* @param blocking true for blocking in-out exchanges, false otherwise
*
* @author Martin Krasser
*/
private[camel] class ConsumerActorRoute(endpointUri: String, id: String, uuid: Boolean) extends ConsumerRoute(endpointUri, id) {
protected override def targetUri = (if (uuid) "actor:uuid:%s" else "actor:id:%s") format id
private[camel] class ConsumerActorRoute(endpointUri: String, uuid: String, blocking: Boolean) extends ConsumerRoute(endpointUri, uuid) {
protected override def targetUri = "actor:uuid:%s?blocking=%s" format (uuid, blocking)
}
/**
* Defines the route to an active object method..
* Defines the route to an typed actor method..
*
* @param endpointUri endpoint URI of the consumer actor method
* @param id active object identifier
* @param id typed actor identifier
* @param method name of the method to invoke.
*
* @author Martin Krasser
*/
private[camel] class ConsumerMethodRoute(val endpointUri: String, id: String, method: String) extends ConsumerRoute(endpointUri, id) {
protected override def targetUri = "%s:%s?method=%s" format (ActiveObjectComponent.InternalSchema, id, method)
protected override def targetUri = "%s:%s?method=%s" format (TypedActorComponent.InternalSchema, id, method)
}
/**
* A registration listener that triggers publication of consumer actors and active object
* A registration listener that triggers publication of consumer actors and typed actor
* methods as well as un-publication of consumer actors. This actor needs to be initialized
* with a <code>PublishRequestorInit</code> command message for obtaining a reference to
* a <code>publisher</code> actor. Before initialization it buffers all outbound messages
@ -210,7 +209,7 @@ private[camel] class PublishRequestor extends Actor {
/**
* Command message to initialize a PublishRequestor to use <code>consumerPublisher</code>
* for publishing actors or active object methods.
* for publishing actors or typed actor methods.
*/
private[camel] case class PublishRequestorInit(consumerPublisher: ActorRef)
@ -226,54 +225,51 @@ private[camel] sealed trait ConsumerEvent
*
* @param actorRef actor reference
* @param uri endpoint URI of the consumer actor
* @param id actor identifier
* @param uuid <code>true</code> if <code>id</code> is the actor's uuid, <code>false</code> if
* <code>id</code> is the actor's id.
* @param uuid actor uuid
* @param blocking true for blocking in-out exchanges, false otherwise
*
* @author Martin Krasser
*/
private[camel] case class ConsumerRegistered(actorRef: ActorRef, uri: String, id: String, uuid: Boolean) extends ConsumerEvent
private[camel] case class ConsumerRegistered(actorRef: ActorRef, uri: String, uuid: String, blocking: Boolean) extends ConsumerEvent
/**
* Event indicating that a consumer actor has been unregistered from the actor registry.
*
* @param actorRef actor reference
* @param uri endpoint URI of the consumer actor
* @param id actor identifier
* @param uuid <code>true</code> if <code>id</code> is the actor's uuid, <code>false</code> if
* <code>id</code> is the actor's id.
* @param uuid actor uuid
*
* @author Martin Krasser
*/
private[camel] case class ConsumerUnregistered(actorRef: ActorRef, uri: String, id: String, uuid: Boolean) extends ConsumerEvent
private[camel] case class ConsumerUnregistered(actorRef: ActorRef, uri: String, uuid: String) extends ConsumerEvent
/**
* Event indicating that an active object proxy has been created for a POJO. For each
* Event indicating that an typed actor proxy has been created for a POJO. For each
* <code>@consume</code> annotated POJO method a separate instance of this class is
* created.
*
* @param activeObject active object (proxy).
* @param typedActor typed actor (proxy).
* @param init
* @param uri endpoint URI of the active object method
* @param uri endpoint URI of the typed actor method
* @param method method to be published.
*
* @author Martin Krasser
*/
private[camel] case class ConsumerMethodRegistered(activeObject: AnyRef, init: AspectInit, uri: String, method: Method) extends ConsumerEvent
private[camel] case class ConsumerMethodRegistered(typedActor: AnyRef, init: AspectInit, uri: String, method: Method) extends ConsumerEvent
/**
* Event indicating that an active object has been stopped. For each
* Event indicating that an typed actor has been stopped. For each
* <code>@consume</code> annotated POJO method a separate instance of this class is
* created.
*
* @param activeObject active object (proxy).
* @param typedActor typed actor (proxy).
* @param init
* @param uri endpoint URI of the active object method
* @param uri endpoint URI of the typed actor method
* @param method method to be un-published.
*
* @author Martin Krasser
*/
private[camel] case class ConsumerMethodUnregistered(activeObject: AnyRef, init: AspectInit, uri: String, method: Method) extends ConsumerEvent
private[camel] case class ConsumerMethodUnregistered(typedActor: AnyRef, init: AspectInit, uri: String, method: Method) extends ConsumerEvent
/**
* @author Martin Krasser
@ -283,9 +279,10 @@ private[camel] object ConsumerRegistered {
* Optionally creates an ConsumerRegistered event message for a consumer actor or None if
* <code>actorRef</code> is not a consumer actor.
*/
def forConsumer(actorRef: ActorRef): Option[ConsumerRegistered] = actorRef match {
case ConsumerDescriptor(ref, uri, id, uuid) => Some(ConsumerRegistered(ref, uri, id, uuid))
case _ => None
def forConsumer(actorRef: ActorRef): Option[ConsumerRegistered] = {
Consumer.forConsumer[ConsumerRegistered](actorRef) {
target => ConsumerRegistered(actorRef, target.endpointUri, actorRef.uuid, target.blocking)
}
}
}
@ -297,9 +294,10 @@ private[camel] object ConsumerUnregistered {
* Optionally creates an ConsumerUnregistered event message for a consumer actor or None if
* <code>actorRef</code> is not a consumer actor.
*/
def forConsumer(actorRef: ActorRef): Option[ConsumerUnregistered] = actorRef match {
case ConsumerDescriptor(ref, uri, id, uuid) => Some(ConsumerUnregistered(ref, uri, id, uuid))
case _ => None
def forConsumer(actorRef: ActorRef): Option[ConsumerUnregistered] = {
Consumer.forConsumer[ConsumerUnregistered](actorRef) {
target => ConsumerUnregistered(actorRef, target.endpointUri, actorRef.uuid)
}
}
}
@ -308,17 +306,17 @@ private[camel] object ConsumerUnregistered {
*/
private[camel] object ConsumerMethod {
/**
* Applies a function <code>f</code> to each consumer method of <code>activeObject</code> and
* Applies a function <code>f</code> to each consumer method of <code>typedActor</code> and
* returns the function results as a list. A consumer method is one that is annotated with
* <code>@consume</code>. If <code>activeObject</code> is a proxy for a remote active object
* <code>@consume</code>. If <code>typedActor</code> is a proxy for a remote typed actor
* <code>f</code> is never called and <code>Nil</code> is returned.
*/
def forConsumer[T](activeObject: AnyRef, init: AspectInit)(f: Method => T): List[T] = {
def forConsumer[T](typedActor: AnyRef, init: AspectInit)(f: Method => T): List[T] = {
// TODO: support consumer annotation inheritance
// - visit overridden methods in superclasses
// - visit implemented method declarations in interfaces
if (init.remoteAddress.isDefined) Nil // let remote node publish active object methods on endpoints
else for (m <- activeObject.getClass.getMethods.toList; if (m.isAnnotationPresent(classOf[consume])))
if (init.remoteAddress.isDefined) Nil // let remote node publish typed actor methods on endpoints
else for (m <- typedActor.getClass.getMethods.toList; if (m.isAnnotationPresent(classOf[consume])))
yield f(m)
}
}
@ -328,56 +326,29 @@ private[camel] object ConsumerMethod {
*/
private[camel] object ConsumerMethodRegistered {
/**
* Creates a list of ConsumerMethodRegistered event messages for an active object or an empty
* list if the active object is a proxy for an remote active object or the active object doesn't
* Creates a list of ConsumerMethodRegistered event messages for an typed actor or an empty
* list if the typed actor is a proxy for an remote typed actor or the typed actor doesn't
* have any <code>@consume</code> annotated methods.
*/
def forConsumer(activeObject: AnyRef, init: AspectInit): List[ConsumerMethodRegistered] = {
ConsumerMethod.forConsumer[ConsumerMethodRegistered](activeObject, init) {
m => ConsumerMethodRegistered(activeObject, init, m.getAnnotation(classOf[consume]).value, m)
}
}
}
private[camel] object ConsumerMethodUnregistered {
/**
* Creates a list of ConsumerMethodUnregistered event messages for an active object or an empty
* list if the active object is a proxy for an remote active object or the active object doesn't
* have any <code>@consume</code> annotated methods.
*/
def forConsumer(activeObject: AnyRef, init: AspectInit): List[ConsumerMethodUnregistered] = {
ConsumerMethod.forConsumer[ConsumerMethodUnregistered](activeObject, init) {
m => ConsumerMethodUnregistered(activeObject, init, m.getAnnotation(classOf[consume]).value, m)
def forConsumer(typedActor: AnyRef, init: AspectInit): List[ConsumerMethodRegistered] = {
ConsumerMethod.forConsumer(typedActor, init) {
m => ConsumerMethodRegistered(typedActor, init, m.getAnnotation(classOf[consume]).value, m)
}
}
}
/**
* Describes a consumer actor with elements that are relevant for publishing an actor at a
* Camel endpoint (or unpublishing an actor from an endpoint).
*
* @author Martin Krasser
*/
private[camel] object ConsumerDescriptor {
private[camel] object ConsumerMethodUnregistered {
/**
* An extractor that optionally creates a 4-tuple from a consumer actor reference containing
* the actor reference itself, endpoint URI, identifier and a hint whether the identifier
* is the actor uuid or actor id. If <code>actorRef</code> doesn't reference a consumer actor,
* None is returned.
* Creates a list of ConsumerMethodUnregistered event messages for an typed actor or an empty
* list if the typed actor is a proxy for an remote typed actor or the typed actor doesn't
* have any <code>@consume</code> annotated methods.
*/
def unapply(actorRef: ActorRef): Option[(ActorRef, String, String, Boolean)] =
unapplyConsumerInstance(actorRef) orElse unapplyConsumeAnnotated(actorRef)
private def unapplyConsumeAnnotated(actorRef: ActorRef): Option[(ActorRef, String, String, Boolean)] = {
val annotation = actorRef.actorClass.getAnnotation(classOf[consume])
if (annotation eq null) None
else if (actorRef.remoteAddress.isDefined) None
else Some((actorRef, annotation.value, actorRef.id, false))
def forConsumer(typedActor: AnyRef, init: AspectInit): List[ConsumerMethodUnregistered] = {
ConsumerMethod.forConsumer(typedActor, init) {
m => ConsumerMethodUnregistered(typedActor, init, m.getAnnotation(classOf[consume]).value, m)
}
}
private def unapplyConsumerInstance(actorRef: ActorRef): Option[(ActorRef, String, String, Boolean)] =
if (!actorRef.actor.isInstanceOf[Consumer]) None
else if (actorRef.remoteAddress.isDefined) None
else Some((actorRef, actorRef.actor.asInstanceOf[Consumer].endpointUri, actorRef.uuid, true))
}

View file

@ -6,13 +6,10 @@ 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 org.apache.camel._
import org.apache.camel.processor.SendProcessor
import se.scalablesolutions.akka.actor.{Actor, ActorRef}
import se.scalablesolutions.akka.dispatch.CompletableFuture
import se.scalablesolutions.akka.util.Logging
/**
* Mixed in by Actor implementations that produce messages to Camel endpoints.
@ -21,15 +18,21 @@ import se.scalablesolutions.akka.util.Logging
*/
trait Producer { this: Actor =>
/**
* Message headers to copy by default from request message to response-message.
*/
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.
* <code>Endpoint</code> object resolved from current CamelContext with
* <code>endpointUri</code>.
*/
def async: Boolean = true
private lazy val endpoint = CamelContextManager.context.getEndpoint(endpointUri)
/**
* <code>SendProcessor</code> for producing messages to <code>endpoint</code>.
*/
private lazy val processor = createSendProcessor
/**
* If set to false (default), this producer expects a response message from the Camel endpoint.
@ -51,146 +54,123 @@ trait Producer { this: Actor =>
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.
* Default implementation of <code>Actor.shutdown</code> for freeing resources needed
* to actually send messages to <code>endpointUri</code>.
*/
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 produceOnewaySync(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 produceSync(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))
override def shutdown {
processor.stop
}
/**
* 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).
* Produces <code>msg</code> as exchange of given <code>pattern</code> to the endpoint specified by
* <code>endpointUri</code>. After producing to the endpoint the processing result is passed as argument
* to <code>receiveAfterProduce</code>. If the result was returned synchronously by the endpoint then
* <code>receiveAfterProduce</code> is called synchronously as well. If the result was returned asynchronously,
* the <code>receiveAfterProduce</code> is called asynchronously as well. This is done by wrapping the result,
* adding it to this producers mailbox, unwrapping it once it is received and calling
* <code>receiveAfterProduce</code>. The original sender and senderFuture are thereby preserved.
*
* @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
* @param msg message to produce
* @param pattern exchange pattern
*/
protected def produceAsync(msg: Any): Unit = {
protected def produce(msg: Any, pattern: ExchangePattern): Unit = {
val cmsg = Message.canonicalize(msg)
val sync = new ProducerResponseSender(
cmsg.headers(headersToCopy), self.sender, self.senderFuture, this)
template.asyncCallback(endpointUri, createInOutExchange.fromRequestMessage(cmsg), sync)
val exchange = createExchange(pattern).fromRequestMessage(cmsg)
processor.process(exchange, new AsyncCallback {
val producer = self
// Need copies of sender and senderFuture references here
// since the callback could be done later by another thread.
val sender = self.sender
val senderFuture = self.senderFuture
def done(doneSync: Boolean): Unit = {
(doneSync, exchange.isFailed) match {
case (true, true) => dispatchSync(exchange.toFailureMessage(cmsg.headers(headersToCopy)))
case (true, false) => dispatchSync(exchange.toResponseMessage(cmsg.headers(headersToCopy)))
case (false, true) => dispatchAsync(FailureResult(exchange.toFailureMessage(cmsg.headers(headersToCopy))))
case (false, false) => dispatchAsync(MessageResult(exchange.toResponseMessage(cmsg.headers(headersToCopy))))
}
}
private def dispatchSync(result: Any) =
receiveAfterProduce(result)
private def dispatchAsync(result: Any) = {
if (senderFuture.isDefined)
producer.postMessageToMailboxAndCreateFutureResultWithTimeout(result, producer.timeout, sender, senderFuture)
else
producer.postMessageToMailbox(result, sender)
}
})
}
/**
* 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>.
* Produces <code>msg</code> to the endpoint specified by <code>endpointUri</code>. Before the message is
* actually produced it is pre-processed by calling <code>receiveBeforeProduce</code>. If <code>oneway</code>
* is true an in-only message exchange is initiated, otherwise an in-out message exchange.
*
* @see Producer#produce(Any, ExchangePattern)
*/
protected def produce: Receive = {
case res: MessageResult => receiveAfterProduce(res.message)
case res: FailureResult => receiveAfterProduce(res.failure)
case msg => {
if ( oneway && !async) produceOnewaySync(msg)
else if ( oneway && async) produceOnewayAsync(msg)
else if (!oneway && !async) self.reply(produceSync(msg))
else /*(!oneway && async)*/ produceAsync(msg)
if (oneway)
produce(receiveBeforeProduce(msg), ExchangePattern.InOnly)
else
produce(receiveBeforeProduce(msg), ExchangePattern.InOut)
}
}
/**
* Called before the message is sent to the endpoint specified by <code>endpointUri</code>. The original
* message is passed as argument. By default, this method simply returns the argument but may be overridden
* by subtraits or subclasses.
*/
protected def receiveBeforeProduce: PartialFunction[Any, Any] = {
case msg => msg
}
/**
* Called after the a result was received from the endpoint specified by <code>endpointUri</code>. The
* result is passed as argument. By default, this method replies the result back to the original sender
* if <code>oneway</code> is false. If <code>oneway</code> is true then nothing is done. This method may
* be overridden by subtraits or subclasses.
*/
protected def receiveAfterProduce: Receive = {
case msg => if (!oneway) self.reply(msg)
}
/**
* Default implementation of Actor.receive
*/
protected def receive = produce
/**
* Creates a new in-only Exchange.
* Creates a new Exchange with given <code>pattern</code> from the endpoint specified by
* <code>endpointUri</code>.
*/
protected def createInOnlyExchange: Exchange = createExchange(ExchangePattern.InOnly)
private def createExchange(pattern: ExchangePattern): Exchange = endpoint.createExchange(pattern)
/**
* Creates a new in-out Exchange.
* Creates a new <code>SendProcessor</code> for <code>endpoint</code>.
*/
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)
private def createSendProcessor = {
val sendProcessor = new SendProcessor(endpoint)
sendProcessor.start
sendProcessor
}
}
/**
* 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
*/
private[camel] class ProducerResponseSender(
headers: Map[String, Any],
sender: Option[ActorRef],
senderFuture: Option[CompletableFuture[Any]],
producer: Actor) extends Synchronization with Logging {
private[camel] case class MessageResult(message: Message)
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) = {
if (senderFuture.isDefined) senderFuture.get completeWithResult message
else if (sender.isDefined) sender.get ! message
else log.warning("No destination for sending response")
}
}
/**
* @author Martin Krasser
*/
private[camel] case class FailureResult(failure: Failure)
/**
* A one-way producer.
@ -201,12 +181,3 @@ trait Oneway extends Producer { this: Actor =>
override def oneway = true
}
/**
* A synchronous producer.
*
* @author Martin Krasser
*/
trait Sync extends Producer { this: Actor =>
override def async = false
}

View file

@ -4,15 +4,25 @@
package se.scalablesolutions.akka.camel.component
import java.lang.{RuntimeException, String}
import java.net.InetSocketAddress
import java.util.{Map => JavaMap}
import java.util.concurrent.TimeoutException
import java.util.concurrent.atomic.AtomicReference
import org.apache.camel.{Exchange, Consumer, Processor}
import jsr166x.Deque
import org.apache.camel._
import org.apache.camel.impl.{DefaultProducer, DefaultEndpoint, DefaultComponent}
import se.scalablesolutions.akka.actor.{ActorRegistry, Actor, ActorRef}
import se.scalablesolutions.akka.camel.{Failure, CamelMessageConversion, Message}
import se.scalablesolutions.akka.dispatch.{CompletableFuture, MessageInvocation, MessageDispatcher}
import se.scalablesolutions.akka.stm.TransactionConfig
import scala.reflect.BeanProperty
import CamelMessageConversion.toExchangeAdapter
import java.lang.Throwable
/**
* Camel component for sending messages to and receiving replies from actors.
@ -41,7 +51,7 @@ class ActorComponent extends DefaultComponent {
/**
* 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>.
* An actor can be referenced by its <code>ActorRef.id</code> or its <code>ActorRef.uuid</code>.
* Supported endpoint URI formats are
* <code>actor:&lt;actorid&gt;</code>,
* <code>actor:id:&lt;actorid&gt;</code> and
@ -57,6 +67,12 @@ class ActorEndpoint(uri: String,
val id: Option[String],
val uuid: Option[String]) extends DefaultEndpoint(uri, comp) {
/**
* Blocking of client thread during two-way message exchanges with consumer actors. This is set
* via the <code>blocking=true|false</code> endpoint URI parameter. If omitted blocking is false.
*/
@BeanProperty var blocking: Boolean = false
/**
* @throws UnsupportedOperationException
*/
@ -75,60 +91,59 @@ class ActorEndpoint(uri: String,
}
/**
* 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.
* Sends the in-message of an exchange to an actor. If the exchange pattern is out-capable and
* <code>blocking</code> is enabled then 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
class ActorProducer(val ep: ActorEndpoint) extends DefaultProducer(ep) with AsyncProcessor {
import ActorProducer._
implicit val sender = None
def process(exchange: Exchange) =
if (exchange.getPattern.isOutCapable) sendSync(exchange) else sendAsync(exchange)
/**
* 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: ActorRef): 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: ActorRef) {
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))
def process(exchange: Exchange, callback: AsyncCallback): Boolean = {
(exchange.getPattern.isOutCapable, ep.blocking) match {
case (true, true) => {
sendSync(exchange)
callback.done(true)
true
}
case (true, false) => {
sendAsync(exchange, Some(AsyncCallbackAdapter(exchange, callback)))
false
}
case (false, _) => {
sendAsync(exchange)
callback.done(true)
true
}
}
}
private def target: Option[ActorRef] =
private def sendSync(exchange: Exchange) = {
val actor = target
val result: Any = actor !! requestFor(exchange)
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 sendAsync(exchange: Exchange, sender: Option[ActorRef] = None) =
target.!(requestFor(exchange))(sender)
private def target =
targetOption getOrElse (throw new ActorNotRegisteredException(ep.getEndpointUri))
private def targetOption: Option[ActorRef] =
if (ep.id.isDefined) targetById(ep.id.get)
else targetByUuid(ep.uuid.get)
@ -141,6 +156,14 @@ class ActorProducer(val ep: ActorEndpoint) extends DefaultProducer(ep) {
private def targetByUuid(uuid: String) = ActorRegistry.actorFor(uuid)
}
/**
* @author Martin Krasser
*/
private[camel] object ActorProducer {
def requestFor(exchange: Exchange) =
exchange.toRequestMessage(Map(Message.MessageExchangeId -> exchange.getExchangeId))
}
/**
* Thrown to indicate that an actor referenced by an endpoint URI cannot be
* found in the ActorRegistry.
@ -150,3 +173,92 @@ class ActorProducer(val ep: ActorEndpoint) extends DefaultProducer(ep) {
class ActorNotRegisteredException(uri: String) extends RuntimeException {
override def getMessage = "%s not registered" format uri
}
/**
* @author Martin Krasser
*/
private[akka] object AsyncCallbackAdapter {
/**
* Creates and starts an <code>AsyncCallbackAdapter</code>.
*
* @param exchange message exchange to write results to.
* @param callback callback object to generate completion notifications.
*/
def apply(exchange: Exchange, callback: AsyncCallback) =
new AsyncCallbackAdapter(exchange, callback).start
}
/**
* Adapts an <code>AsyncCallback</code> to <code>ActorRef.!</code>. Used by other actors to reply
* asynchronously to Camel with <code>ActorRef.reply</code>.
* <p>
* <em>Please note</em> that this adapter can only be used locally at the moment which should not
* be a problem is most situations as Camel endpoints are only activated for local actor references,
* never for remote references.
*
* @author Martin Krasser
*/
private[akka] class AsyncCallbackAdapter(exchange: Exchange, callback: AsyncCallback) extends ActorRef {
def start = {
_isRunning = true
this
}
def stop() = {
_isRunning = false
_isShutDown = true
}
/**
* Writes the reply <code>message</code> to <code>exchange</code> and uses <code>callback</code> to
* generate completion notifications.
*
* @param message reply message
* @param sender ignored
*/
protected[akka] def postMessageToMailbox(message: Any, senderOption: Option[ActorRef]) = {
message match {
case msg: Failure => exchange.fromFailureMessage(msg)
case msg => exchange.fromResponseMessage(Message.canonicalize(msg))
}
callback.done(false)
}
def actorClass: Class[_ <: Actor] = unsupported
def actorClassName = unsupported
def dispatcher_=(md: MessageDispatcher): Unit = unsupported
def dispatcher: MessageDispatcher = unsupported
def transactionConfig_=(config: TransactionConfig): Unit = unsupported
def transactionConfig: TransactionConfig = unsupported
def makeTransactionRequired: Unit = unsupported
def makeRemote(hostname: String, port: Int): Unit = unsupported
def makeRemote(address: InetSocketAddress): Unit = unsupported
def homeAddress_=(address: InetSocketAddress): Unit = unsupported
def remoteAddress: Option[InetSocketAddress] = unsupported
def link(actorRef: ActorRef): Unit = unsupported
def unlink(actorRef: ActorRef): Unit = unsupported
def startLink(actorRef: ActorRef): Unit = unsupported
def startLinkRemote(actorRef: ActorRef, hostname: String, port: Int): Unit = unsupported
def spawn[T <: Actor : Manifest]: ActorRef = unsupported
def spawnRemote[T <: Actor: Manifest](hostname: String, port: Int): ActorRef = unsupported
def spawnLink[T <: Actor: Manifest]: ActorRef = unsupported
def spawnLinkRemote[T <: Actor : Manifest](hostname: String, port: Int): ActorRef = unsupported
def shutdownLinkedActors: Unit = unsupported
def supervisor: Option[ActorRef] = unsupported
protected[akka] def postMessageToMailboxAndCreateFutureResultWithTimeout[T](message: Any, timeout: Long, senderOption: Option[ActorRef], senderFuture: Option[CompletableFuture[T]]) = unsupported
protected[akka] def mailbox: AnyRef = unsupported
protected[akka] def mailbox_=(msg: AnyRef):AnyRef = unsupported
protected[akka] def restart(reason: Throwable, maxNrOfRetries: Int, withinTimeRange: Int): Unit = unsupported
protected[akka] def restartLinkedActors(reason: Throwable, maxNrOfRetries: Int, withinTimeRange: Int): Unit = unsupported
protected[akka] def handleTrapExit(dead: ActorRef, reason: Throwable): Unit = unsupported
protected[akka] def linkedActors: JavaMap[String, ActorRef] = unsupported
protected[akka] def linkedActorsAsList: List[ActorRef] = unsupported
protected[akka] def invoke(messageHandle: MessageInvocation): Unit = unsupported
protected[akka] def remoteAddress_=(addr: Option[InetSocketAddress]): Unit = unsupported
protected[akka] def registerSupervisorAsRemoteActor = unsupported
protected[akka] def supervisor_=(sup: Option[ActorRef]): Unit = unsupported
protected[this] def actorInstance: AtomicReference[Actor] = unsupported
private def unsupported = throw new UnsupportedOperationException("Not supported for %s" format classOf[AsyncCallbackAdapter].getName)
}

View file

@ -12,31 +12,31 @@ import org.apache.camel.component.bean._
/**
* @author Martin Krasser
*/
object ActiveObjectComponent {
object TypedActorComponent {
/**
* Default schema name for active object endpoint URIs.
* Default schema name for typed actor endpoint URIs.
*/
val InternalSchema = "active-object-internal"
val InternalSchema = "typed-actor-internal"
}
/**
* Camel component for exchanging messages with active objects. This component
* tries to obtain the active object from the <code>activeObjectRegistry</code>
* Camel component for exchanging messages with typed actors. This component
* tries to obtain the typed actor from the <code>typedActorRegistry</code>
* first. If it's not there it tries to obtain it from the CamelContext's registry.
*
* @see org.apache.camel.component.bean.BeanComponent
*
* @author Martin Krasser
*/
class ActiveObjectComponent extends BeanComponent {
val activeObjectRegistry = new ConcurrentHashMap[String, AnyRef]
class TypedActorComponent extends BeanComponent {
val typedActorRegistry = new ConcurrentHashMap[String, AnyRef]
/**
* Creates a {@link org.apache.camel.component.bean.BeanEndpoint} with a custom
* bean holder that uses <code>activeObjectRegistry</code> for getting access to
* active objects (beans).
* bean holder that uses <code>typedActorRegistry</code> for getting access to
* typed actors (beans).
*
* @see se.scalablesolutions.akka.camel.component.ActiveObjectHolder
* @see se.scalablesolutions.akka.camel.component.TypedActorHolder
*/
override def createEndpoint(uri: String, remaining: String, parameters: Map[String, AnyRef]) = {
val endpoint = new BeanEndpoint(uri, this)
@ -47,39 +47,39 @@ class ActiveObjectComponent extends BeanComponent {
}
private def createBeanHolder(beanName: String) =
new ActiveObjectHolder(activeObjectRegistry, getCamelContext, beanName).createCacheHolder
new TypedActorHolder(typedActorRegistry, getCamelContext, beanName).createCacheHolder
}
/**
* {@link org.apache.camel.component.bean.BeanHolder} implementation that uses a custom
* registry for getting access to active objects.
* registry for getting access to typed actors.
*
* @author Martin Krasser
*/
class ActiveObjectHolder(activeObjectRegistry: Map[String, AnyRef], context: CamelContext, name: String)
class TypedActorHolder(typedActorRegistry: Map[String, AnyRef], context: CamelContext, name: String)
extends RegistryBean(context, name) {
/**
* Returns an {@link se.scalablesolutions.akka.camel.component.ActiveObjectInfo} instance.
* Returns an {@link se.scalablesolutions.akka.camel.component.TypedActorInfo} instance.
*/
override def getBeanInfo: BeanInfo =
new ActiveObjectInfo(getContext, getBean.getClass, getParameterMappingStrategy)
new TypedActorInfo(getContext, getBean.getClass, getParameterMappingStrategy)
/**
* Obtains an active object from <code>activeObjectRegistry</code>.
* Obtains an typed actor from <code>typedActorRegistry</code>.
*/
override def getBean: AnyRef = {
val bean = activeObjectRegistry.get(getName)
val bean = typedActorRegistry.get(getName)
if (bean eq null) super.getBean else bean
}
}
/**
* Provides active object meta information.
* Provides typed actor meta information.
*
* @author Martin Krasser
*/
class ActiveObjectInfo(context: CamelContext, clazz: Class[_], strategy: ParameterMappingStrategy)
class TypedActorInfo(context: CamelContext, clazz: Class[_], strategy: ParameterMappingStrategy)
extends BeanInfo(context, clazz, strategy) {
/**

View file

@ -1,34 +1,28 @@
package se.scalablesolutions.akka.camel;
import org.apache.camel.Body;
import org.apache.camel.Header;
import se.scalablesolutions.akka.actor.annotation.consume;
import se.scalablesolutions.akka.actor.TypedActor;
/**
* @author Martin Krasser
*/
public class PojoBase {
public class PojoBase extends TypedActor implements PojoBaseIntf {
public String m1(String b, String h) {
return "m1base: " + b + " " + h;
}
@consume("direct:m2base")
public String m2(@Body String b, @Header("test") String h) {
public String m2(String b, String h) {
return "m2base: " + b + " " + h;
}
@consume("direct:m3base")
public String m3(@Body String b, @Header("test") String h) {
public String m3(String b, String h) {
return "m3base: " + b + " " + h;
}
@consume("direct:m4base")
public String m4(@Body String b, @Header("test") String h) {
public String m4(String b, String h) {
return "m4base: " + b + " " + h;
}
public void m5(@Body String b, @Header("test") String h) {
public void m5(String b, String h) {
}
}

View file

@ -0,0 +1,21 @@
package se.scalablesolutions.akka.camel;
import org.apache.camel.Body;
import org.apache.camel.Header;
import se.scalablesolutions.akka.actor.annotation.consume;
/**
* @author Martin Krasser
*/
public interface PojoBaseIntf {
public String m1(String b, String h);
@consume("direct:m2base")
public String m2(@Body String b, @Header("test") String h);
@consume("direct:m3base")
public String m3(@Body String b, @Header("test") String h);
@consume("direct:m4base")
public String m4(@Body String b, @Header("test") String h);
public void m5(@Body String b, @Header("test") String h);
}

View file

@ -1,23 +1,17 @@
package se.scalablesolutions.akka.camel;
import org.apache.camel.Body;
import org.apache.camel.Header;
import se.scalablesolutions.akka.actor.annotation.consume;
import se.scalablesolutions.akka.actor.TypedActor;
/**
* @author Martin Krasser
*/
public class PojoImpl implements PojoIntf {
public class PojoImpl extends TypedActor implements PojoIntf {
public String m1(String b, String h) {
return "m1impl: " + b + " " + h;
}
@consume("direct:m2impl")
public String m2(@Body String b, @Header("test") String h) {
public String m2(String b, String h) {
return "m2impl: " + b + " " + h;
}
}

View file

@ -1,11 +1,11 @@
package se.scalablesolutions.akka.camel;
import se.scalablesolutions.akka.actor.annotation.consume;
import se.scalablesolutions.akka.actor.*;
/**
* @author Martin Krasser
*/
public class Pojo {
public class PojoNonConsumer extends TypedActor implements PojoNonConsumerIntf {
public String foo(String s) {
return String.format("foo: %s", s);

View file

@ -0,0 +1,9 @@
package se.scalablesolutions.akka.camel;
/**
* @author Martin Krasser
*/
public interface PojoNonConsumerIntf {
public String foo(String s);
}

View file

@ -1,15 +1,14 @@
package se.scalablesolutions.akka.camel;
import se.scalablesolutions.akka.actor.annotation.consume;
import se.scalablesolutions.akka.actor.TypedActor;
/**
* @author Martin Krasser
*/
public class PojoRemote {
public class PojoRemote extends TypedActor implements PojoRemoteIntf {
@consume("direct:remote-active-object")
public String foo(String s) {
return String.format("remote active object: %s", s);
return String.format("remote typed actor: %s", s);
}
}

View file

@ -0,0 +1,12 @@
package se.scalablesolutions.akka.camel;
import se.scalablesolutions.akka.actor.annotation.consume;
/**
* @author Martin Krasser
*/
public interface PojoRemoteIntf {
@consume("direct:remote-typed-actor")
public String foo(String s);
}

View file

@ -1,13 +1,12 @@
package se.scalablesolutions.akka.camel;
import se.scalablesolutions.akka.actor.annotation.consume;
import se.scalablesolutions.akka.actor.TypedActor;
/**
* @author Martin Krasser
*/
public class PojoSingle {
public class PojoSingle extends TypedActor implements PojoSingleIntf {
@consume("direct:foo")
public void foo(String b) {
}

View file

@ -0,0 +1,12 @@
package se.scalablesolutions.akka.camel;
import se.scalablesolutions.akka.actor.annotation.consume;
/**
* @author Martin Krasser
*/
public interface PojoSingleIntf {
@consume("direct:foo")
public void foo(String b);
}

View file

@ -1,15 +1,11 @@
package se.scalablesolutions.akka.camel;
import org.apache.camel.Body;
import org.apache.camel.Header;
import se.scalablesolutions.akka.actor.TypedActor;
import se.scalablesolutions.akka.actor.annotation.consume;
public class PojoSub extends PojoBase {
public class PojoSub extends PojoBase implements PojoSubIntf {
@Override
@consume("direct:m1sub")
public String m1(@Body String b, @Header("test") String h) {
public String m1(String b, String h) {
return "m1sub: " + b + " " + h;
}
@ -19,8 +15,7 @@ public class PojoSub extends PojoBase {
}
@Override
@consume("direct:m3sub")
public String m3(@Body String b, @Header("test") String h) {
public String m3(String b, String h) {
return "m3sub: " + b + " " + h;
}

View file

@ -0,0 +1,18 @@
package se.scalablesolutions.akka.camel;
import org.apache.camel.Body;
import org.apache.camel.Header;
import se.scalablesolutions.akka.actor.annotation.consume;
public interface PojoSubIntf extends PojoBaseIntf {
@consume("direct:m1sub")
public String m1(@Body String b, @Header("test") String h);
@Override
public String m2(String b, String h);
@Override
@consume("direct:m3sub")
public String m3(@Body String b, @Header("test") String h);
}

View file

@ -1,12 +1,13 @@
package se.scalablesolutions.akka.camel
import java.util.concurrent.{CountDownLatch, TimeUnit}
import java.util.concurrent.{TimeoutException, CountDownLatch, TimeUnit}
import org.apache.camel.CamelExecutionException
import org.apache.camel.builder.RouteBuilder
import org.scalatest.{GivenWhenThen, BeforeAndAfterAll, FeatureSpec}
import se.scalablesolutions.akka.actor.Actor._
import se.scalablesolutions.akka.actor.{ActiveObject, Actor, ActorRegistry}
import se.scalablesolutions.akka.actor.{TypedActor, Actor, ActorRegistry}
class CamelServiceFeatureTest extends FeatureSpec with BeforeAndAfterAll with GivenWhenThen {
import CamelServiceFeatureTest._
@ -16,7 +17,7 @@ class CamelServiceFeatureTest extends FeatureSpec with BeforeAndAfterAll with Gi
override protected def beforeAll = {
ActorRegistry.shutdownAll
// create new CamelService instance
service = CamelService.newInstance
service = CamelServiceFactory.createCamelService
// register test consumer before starting the CamelService
actorOf(new TestConsumer("direct:publish-test-1")).start
// Configure a custom camel route
@ -26,7 +27,7 @@ class CamelServiceFeatureTest extends FeatureSpec with BeforeAndAfterAll with Gi
// count expectations in the next step (needed for testing only).
service.consumerPublisher.start
// set expectations on publish count
val latch = (service.consumerPublisher !! SetExpectedMessageCount(1)).as[CountDownLatch].get
val latch = service.expectEndpointActivationCount(1)
// start the CamelService
service.load
// await publication of first test consumer
@ -40,10 +41,10 @@ class CamelServiceFeatureTest extends FeatureSpec with BeforeAndAfterAll with Gi
feature("Publish registered consumer actors in the global CamelContext") {
scenario("access registered consumer actors via Camel direct-endpoints") {
scenario("access non-blocking consumer actors via Camel direct-endpoints") {
given("two consumer actors registered before and after CamelService startup")
val latch = (service.consumerPublisher !! SetExpectedMessageCount(1)).as[CountDownLatch].get
val latch = service.expectEndpointActivationCount(1)
actorOf(new TestConsumer("direct:publish-test-2")).start
assert(latch.await(5000, TimeUnit.MILLISECONDS))
@ -55,6 +56,25 @@ class CamelServiceFeatureTest extends FeatureSpec with BeforeAndAfterAll with Gi
assert(response1 === "received msg1")
assert(response2 === "received msg2")
}
scenario("access blocking, non-responding consumer actor via a Camel direct-endpoint") {
given("a consumer actor registered after CamelService startup")
val latch = service.expectEndpointActivationCount(1)
actorOf(new TestBlocker("direct:publish-test-3")).start
assert(latch.await(5000, TimeUnit.MILLISECONDS))
try {
when("a request is sent to this actor")
CamelContextManager.template.requestBody("direct:publish-test-3", "msg3")
fail("expected TimoutException not thrown")
} catch {
case e => {
then("a TimoutException should be thrown")
assert(e.getCause.isInstanceOf[TimeoutException])
}
}
}
}
feature("Unpublish registered consumer actor from the global CamelContext") {
@ -62,24 +82,22 @@ class CamelServiceFeatureTest extends FeatureSpec with BeforeAndAfterAll with Gi
scenario("access to unregistered consumer actor via Camel direct-endpoint fails") {
val endpointUri = "direct:unpublish-test-1"
given("a consumer actor that has been stopped")
given("a consumer actor registered after CamelService startup")
assert(CamelContextManager.context.hasEndpoint(endpointUri) eq null)
var latch = (service.consumerPublisher !! SetExpectedMessageCount(1)).as[CountDownLatch].get
var latch = service.expectEndpointActivationCount(1)
val consumer = actorOf(new TestConsumer(endpointUri)).start
assert(latch.await(5000, TimeUnit.MILLISECONDS))
assert(CamelContextManager.context.hasEndpoint(endpointUri) ne null)
latch = (service.consumerPublisher !! SetExpectedMessageCount(1)).as[CountDownLatch].get
when("the actor is stopped")
latch = service.expectEndpointDeactivationCount(1)
consumer.stop
assert(latch.await(5000, TimeUnit.MILLISECONDS))
// endpoint is still there but the route has been stopped
assert(CamelContextManager.context.hasEndpoint(endpointUri) ne null)
when("a request is sent to this actor")
val response1 = CamelContextManager.template.requestBody(endpointUri, "msg1")
then("the direct-endpoint falls back to its default behaviour and returns the original message")
assert(response1 === "msg1")
then("the associated endpoint isn't accessible any more")
intercept[CamelExecutionException] {
CamelContextManager.template.requestBody(endpointUri, "msg1")
}
}
}
@ -98,13 +116,13 @@ class CamelServiceFeatureTest extends FeatureSpec with BeforeAndAfterAll with Gi
}
}
feature("Publish active object methods in the global CamelContext") {
feature("Publish typed actor methods in the global CamelContext") {
scenario("access active object methods via Camel direct-endpoints") {
scenario("access typed actor methods via Camel direct-endpoints") {
given("an active object registered after CamelService startup")
var latch = (service.consumerPublisher !! SetExpectedMessageCount(3)).as[CountDownLatch].get
val obj = ActiveObject.newInstance(classOf[PojoBase])
given("an typed actor registered after CamelService startup")
var latch = service.expectEndpointActivationCount(3)
val obj = TypedActor.newInstance(classOf[PojoBaseIntf], classOf[PojoBase])
assert(latch.await(5000, TimeUnit.MILLISECONDS))
when("requests are sent to published methods")
@ -117,35 +135,37 @@ class CamelServiceFeatureTest extends FeatureSpec with BeforeAndAfterAll with Gi
assert(response2 === "m3base: x y")
assert(response3 === "m4base: x y")
// cleanup to avoid conflicts with next test (i.e. avoid multiple consumers on direct-endpoints)
latch = (service.consumerPublisher !! SetExpectedMessageCount(3)).as[CountDownLatch].get
ActiveObject.stop(obj)
// cleanup to avoid conflicts with next test (i.e. avoid multiple consumers on direct-endpoints)
latch = service.expectEndpointDeactivationCount(3)
TypedActor.stop(obj)
assert(latch.await(5000, TimeUnit.MILLISECONDS))
}
}
feature("Unpublish active object method from the global CamelContext") {
feature("Unpublish typed actor method from the global CamelContext") {
scenario("access to unregistered active object methof via Camel direct-endpoint fails") {
scenario("access to unregistered typed actor method via Camel direct-endpoint fails") {
given("an active object that has been stopped")
var latch = (service.consumerPublisher !! SetExpectedMessageCount(3)).as[CountDownLatch].get
val obj = ActiveObject.newInstance(classOf[PojoBase])
given("an typed actor registered after CamelService startup")
var latch = service.expectEndpointActivationCount(3)
val obj = TypedActor.newInstance(classOf[PojoBaseIntf], classOf[PojoBase])
assert(latch.await(5000, TimeUnit.MILLISECONDS))
latch = (service.consumerPublisher !! SetExpectedMessageCount(3)).as[CountDownLatch].get
ActiveObject.stop(obj)
when("the typed actor is stopped")
latch = service.expectEndpointDeactivationCount(3)
TypedActor.stop(obj)
assert(latch.await(5000, TimeUnit.MILLISECONDS))
when("requests are sent to published methods")
val response1 = CamelContextManager.template.requestBodyAndHeader("direct:m2base", "x", "test", "y")
val response2 = CamelContextManager.template.requestBodyAndHeader("direct:m3base", "x", "test", "y")
val response3 = CamelContextManager.template.requestBodyAndHeader("direct:m4base", "x", "test", "y")
then("the direct-endpoints fall back to their default behaviour and return the original message")
assert(response1 === "x")
assert(response2 === "x")
assert(response3 === "x")
then("the associated endpoints aren't accessible any more")
intercept[CamelExecutionException] {
CamelContextManager.template.requestBodyAndHeader("direct:m2base", "x", "test", "y")
}
intercept[CamelExecutionException] {
CamelContextManager.template.requestBodyAndHeader("direct:m3base", "x", "test", "y")
}
intercept[CamelExecutionException] {
CamelContextManager.template.requestBodyAndHeader("direct:m4base", "x", "test", "y")
}
}
}
}
@ -159,6 +179,15 @@ object CamelServiceFeatureTest {
}
}
class TestBlocker(uri: String) extends Actor with Consumer {
self.timeout = 1000
def endpointUri = uri
override def blocking = true
protected def receive = {
case msg: Message => { /* do not reply */ }
}
}
class TestActor extends Actor {
self.id = "custom-actor-id"
protected def receive = {

View file

@ -4,7 +4,7 @@ import java.net.InetSocketAddress
import org.scalatest.junit.JUnitSuite
import se.scalablesolutions.akka.actor.{AspectInit, ActiveObject}
import se.scalablesolutions.akka.actor.{AspectInit, TypedActor}
import se.scalablesolutions.akka.camel.ConsumerMethodRegistered._
import org.junit.{AfterClass, Test}
@ -12,8 +12,8 @@ class ConsumerMethodRegisteredTest extends JUnitSuite {
import ConsumerMethodRegisteredTest._
val remoteAddress = new InetSocketAddress("localhost", 8888);
val remoteAspectInit = AspectInit(classOf[String], null, Some(remoteAddress), 1000)
val localAspectInit = AspectInit(classOf[String], null, None, 1000)
val remoteAspectInit = AspectInit(classOf[String], null, null, Some(remoteAddress), 1000)
val localAspectInit = AspectInit(classOf[String], null, null, None, 1000)
val ascendingMethodName = (r1: ConsumerMethodRegistered, r2: ConsumerMethodRegistered) =>
r1.method.getName < r2.method.getName
@ -44,14 +44,14 @@ class ConsumerMethodRegisteredTest extends JUnitSuite {
}
object ConsumerMethodRegisteredTest {
val activePojoBase = ActiveObject.newInstance(classOf[PojoBase])
val activePojoSub = ActiveObject.newInstance(classOf[PojoSub])
val activePojoIntf = ActiveObject.newInstance(classOf[PojoIntf], new PojoImpl)
val activePojoBase = TypedActor.newInstance(classOf[PojoBaseIntf], classOf[PojoBase])
val activePojoSub = TypedActor.newInstance(classOf[PojoSubIntf], classOf[PojoSub])
val activePojoIntf = TypedActor.newInstance(classOf[PojoIntf], classOf[PojoImpl])
@AfterClass
def afterClass = {
ActiveObject.stop(activePojoBase)
ActiveObject.stop(activePojoSub)
ActiveObject.stop(activePojoIntf)
TypedActor.stop(activePojoBase)
TypedActor.stop(activePojoSub)
TypedActor.stop(activePojoIntf)
}
}

View file

@ -5,17 +5,16 @@ import org.scalatest.junit.JUnitSuite
import se.scalablesolutions.akka.actor.Actor
import se.scalablesolutions.akka.actor.Actor._
import se.scalablesolutions.akka.actor.annotation.consume
object ConsumerRegisteredTest {
@consume("mock:test1")
class ConsumeAnnotatedActor extends Actor {
self.id = "test"
class ConsumerActor1 extends Actor with Consumer {
def endpointUri = "mock:test1"
protected def receive = null
}
class ConsumerActor extends Actor with Consumer {
class ConsumerActor2 extends Actor with Consumer {
def endpointUri = "mock:test2"
override def blocking = true
protected def receive = null
}
@ -27,21 +26,14 @@ object ConsumerRegisteredTest {
class ConsumerRegisteredTest extends JUnitSuite {
import ConsumerRegisteredTest._
@Test def shouldCreatePublishRequestList = {
val a = actorOf[ConsumeAnnotatedActor]
val as = List(a)
val events = for (a <- as; e <- ConsumerRegistered.forConsumer(a)) yield e
assert(events === List(ConsumerRegistered(a, "mock:test1", "test", false)))
@Test def shouldCreateSomeNonBlockingPublishRequest = {
val ca = actorOf[ConsumerActor1]
val event = ConsumerRegistered.forConsumer(ca)
assert(event === Some(ConsumerRegistered(ca, "mock:test1", ca.uuid, false)))
}
@Test def shouldCreateSomePublishRequestWithActorId = {
val a = actorOf[ConsumeAnnotatedActor]
val event = ConsumerRegistered.forConsumer(a)
assert(event === Some(ConsumerRegistered(a, "mock:test1", "test", false)))
}
@Test def shouldCreateSomePublishRequestWithActorUuid = {
val ca = actorOf[ConsumerActor]
@Test def shouldCreateSomeBlockingPublishRequest = {
val ca = actorOf[ConsumerActor2]
val event = ConsumerRegistered.forConsumer(ca)
assert(event === Some(ConsumerRegistered(ca, "mock:test2", ca.uuid, true)))
}

View file

@ -5,14 +5,8 @@ import org.apache.camel.builder.RouteBuilder
import org.apache.camel.component.mock.MockEndpoint
import org.scalatest.{GivenWhenThen, BeforeAndAfterEach, BeforeAndAfterAll, FeatureSpec}
import se.scalablesolutions.akka.actor.{Actor, ActorRegistry}
import se.scalablesolutions.akka.actor.Actor._
object ProducerFeatureTest {
class TestProducer(uri: String) extends Actor with Producer {
def endpointUri = uri
}
}
import se.scalablesolutions.akka.actor.{ActorRef, Actor, ActorRegistry}
class ProducerFeatureTest extends FeatureSpec with BeforeAndAfterAll with BeforeAndAfterEach with GivenWhenThen {
import ProducerFeatureTest._
@ -24,109 +18,276 @@ class ProducerFeatureTest extends FeatureSpec with BeforeAndAfterAll with Before
CamelContextManager.start
}
override protected def afterAll = CamelContextManager.stop
override protected def afterAll = {
CamelContextManager.stop
ActorRegistry.shutdownAll
}
override protected def afterEach = {
mockEndpoint.reset
ActorRegistry.shutdownAll
}
feature("Produce a message to a Camel endpoint") {
scenario("produce message sync and receive response") {
given("a registered synchronous two-way producer for endpoint direct:producer-test-2")
val producer = actorOf(new TestProducer("direct:producer-test-2") with Sync)
scenario("produce message and receive normal response") {
given("a registered two-way producer")
val producer = actorOf(new TestProducer("direct:producer-test-2", true))
producer.start
when("a test message is sent to the producer")
when("a test message is sent to the producer with !!")
val message = Message("test", Map(Message.MessageExchangeId -> "123"))
val result = producer !! message
then("the expected result message should be returned including a correlation identifier")
val expected = Message("received test", Map(Message.MessageExchangeId -> "123"))
then("a normal response should have been returned by the producer")
val expected = Message("received TEST", Map(Message.MessageExchangeId -> "123"))
assert(result === Some(expected))
}
scenario("produce message async and receive response") {
given("a registered asynchronous two-way producer for endpoint direct:producer-test-2")
scenario("produce message and receive failure response") {
given("a registered two-way producer")
val producer = actorOf(new TestProducer("direct:producer-test-2"))
producer.start
when("a test message is sent to the producer")
val message = Message("test", Map(Message.MessageExchangeId -> "123"))
val result = producer !! message
then("the expected result message should be returned including a correlation identifier")
val expected = Message("received test", Map(Message.MessageExchangeId -> "123"))
assert(result === Some(expected))
}
scenario("produce message sync and receive failure") {
given("a registered synchronous two-way producer for endpoint direct:producer-test-2")
val producer = actorOf(new TestProducer("direct:producer-test-2") with Sync)
producer.start
when("a fail message is sent to the producer")
when("a test message causing an exception is sent to the producer with !!")
val message = Message("fail", Map(Message.MessageExchangeId -> "123"))
val result = (producer !! message).as[Failure]
then("the expected failure message should be returned including a correlation identifier")
then("a failure response should have been returned by the producer")
val expectedFailureText = result.get.cause.getMessage
val expectedHeaders = result.get.headers
assert(expectedFailureText === "failure")
assert(expectedHeaders === Map(Message.MessageExchangeId -> "123"))
}
scenario("produce message async and receive failure") {
given("a registered asynchronous two-way producer for endpoint direct:producer-test-2")
val producer = actorOf(new TestProducer("direct:producer-test-2"))
scenario("produce message oneway") {
given("a registered one-way producer")
val producer = actorOf(new TestProducer("direct:producer-test-1", true) with Oneway)
producer.start
when("a fail message is sent to the producer")
val message = Message("fail", Map(Message.MessageExchangeId -> "123"))
val result = (producer !! message).as[Failure]
then("the expected failure message should be returned including a correlation identifier")
val expectedFailureText = result.get.cause.getMessage
val expectedHeaders = result.get.headers
assert(expectedFailureText === "failure")
assert(expectedHeaders === Map(Message.MessageExchangeId -> "123"))
}
scenario("produce message sync oneway") {
given("a registered synchronous one-way producer for endpoint direct:producer-test-1")
val producer = actorOf(new TestProducer("direct:producer-test-1") with Sync with Oneway)
producer.start
when("a test message is sent to the producer")
mockEndpoint.expectedBodiesReceived("test")
when("a test message is sent to the producer with !")
mockEndpoint.expectedBodiesReceived("TEST")
producer ! Message("test")
then("the expected message should have been sent to mock:mock")
then("the test message should have been sent to mock:mock")
mockEndpoint.assertIsSatisfied
}
scenario("produce message async oneway") {
given("a registered asynchronous one-way producer for endpoint direct:producer-test-1")
val producer = actorOf(new TestProducer("direct:producer-test-1") with Oneway)
scenario("produce message twoway without sender reference") {
given("a registered two-way producer")
val producer = actorOf(new TestProducer("direct:producer-test-1"))
producer.start
when("a test message is sent to the producer")
when("a test message is sent to the producer with !")
mockEndpoint.expectedBodiesReceived("test")
producer ! Message("test")
then("the expected message should have been sent to mock:mock")
then("there should be only a warning that there's no sender reference")
mockEndpoint.assertIsSatisfied
}
}
feature("Produce a message to an async Camel endpoint") {
scenario("produce message and async receive normal response") {
given("a registered two-way producer")
val producer = actorOf(new TestProducer("direct:producer-test-3"))
producer.start
when("a test message is sent to the producer with !!")
val message = Message("test", Map(Message.MessageExchangeId -> "123"))
val result = producer !! message
then("a normal response should have been returned by the producer")
val expected = Message("received test", Map(Message.MessageExchangeId -> "123"))
assert(result === Some(expected))
}
scenario("produce message and async receive failure response") {
given("a registered two-way producer")
val producer = actorOf(new TestProducer("direct:producer-test-3"))
producer.start
when("a test message causing an exception is sent to the producer with !!")
val message = Message("fail", Map(Message.MessageExchangeId -> "123"))
val result = (producer !! message).as[Failure]
then("a failure response should have been returned by the producer")
val expectedFailureText = result.get.cause.getMessage
val expectedHeaders = result.get.headers
assert(expectedFailureText === "failure")
assert(expectedHeaders === Map(Message.MessageExchangeId -> "123"))
}
}
feature("Produce a message to a Camel endpoint and then forward the result") {
scenario("produce message, forward and receive normal response") {
given("a registered two-way producer configured with a forward target")
val target = actorOf[ReplyingForwardTarget].start
val producer = actorOf(new TestForwarder("direct:producer-test-2", target)).start
when("a test message is sent to the producer with !!")
val message = Message("test", Map(Message.MessageExchangeId -> "123"))
val result = producer !! message
then("a normal response should have been returned by the forward target")
val expected = Message("received test", Map(Message.MessageExchangeId -> "123", "test" -> "result"))
assert(result === Some(expected))
}
scenario("produce message, forward and receive failure response") {
given("a registered two-way producer configured with a forward target")
val target = actorOf[ReplyingForwardTarget].start
val producer = actorOf(new TestForwarder("direct:producer-test-2", target)).start
when("a test message causing an exception is sent to the producer with !!")
val message = Message("fail", Map(Message.MessageExchangeId -> "123"))
val result = (producer !! message).as[Failure]
then("a failure response should have been returned by the forward target")
val expectedFailureText = result.get.cause.getMessage
val expectedHeaders = result.get.headers
assert(expectedFailureText === "failure")
assert(expectedHeaders === Map(Message.MessageExchangeId -> "123", "test" -> "failure"))
}
scenario("produce message, forward and produce normal response") {
given("a registered one-way producer configured with a forward target")
val target = actorOf[ProducingForwardTarget].start
val producer = actorOf(new TestForwarder("direct:producer-test-2", target)).start
when("a test message is sent to the producer with !")
mockEndpoint.expectedBodiesReceived("received test")
val result = producer.!(Message("test"))(Some(producer))
then("a normal response should have been produced by the forward target")
mockEndpoint.assertIsSatisfied
}
scenario("produce message, forward and produce failure response") {
given("a registered one-way producer configured with a forward target")
val target = actorOf[ProducingForwardTarget].start
val producer = actorOf(new TestForwarder("direct:producer-test-2", target)).start
when("a test message causing an exception is sent to the producer with !")
mockEndpoint.expectedMessageCount(1)
mockEndpoint.message(0).body().isInstanceOf(classOf[Failure])
val result = producer.!(Message("fail"))(Some(producer))
then("a failure response should have been produced by the forward target")
mockEndpoint.assertIsSatisfied
}
}
feature("Produce a message to an async Camel endpoint and then forward the result") {
scenario("produce message, forward and async receive normal response") {
given("a registered two-way producer configured with a forward target")
val target = actorOf[ReplyingForwardTarget].start
val producer = actorOf(new TestForwarder("direct:producer-test-3", target)).start
when("a test message is sent to the producer with !!")
val message = Message("test", Map(Message.MessageExchangeId -> "123"))
val result = producer !! message
then("a normal response should have been returned by the forward target")
val expected = Message("received test", Map(Message.MessageExchangeId -> "123", "test" -> "result"))
assert(result === Some(expected))
}
scenario("produce message, forward and async receive failure response") {
given("a registered two-way producer configured with a forward target")
val target = actorOf[ReplyingForwardTarget].start
val producer = actorOf(new TestForwarder("direct:producer-test-3", target)).start
when("a test message causing an exception is sent to the producer with !!")
val message = Message("fail", Map(Message.MessageExchangeId -> "123"))
val result = (producer !! message).as[Failure]
then("a failure response should have been returned by the forward target")
val expectedFailureText = result.get.cause.getMessage
val expectedHeaders = result.get.headers
assert(expectedFailureText === "failure")
assert(expectedHeaders === Map(Message.MessageExchangeId -> "123", "test" -> "failure"))
}
scenario("produce message, forward and async produce normal response") {
given("a registered one-way producer configured with a forward target")
val target = actorOf[ProducingForwardTarget].start
val producer = actorOf(new TestForwarder("direct:producer-test-3", target)).start
when("a test message is sent to the producer with !")
mockEndpoint.expectedBodiesReceived("received test")
val result = producer.!(Message("test"))(Some(producer))
then("a normal response should have been produced by the forward target")
mockEndpoint.assertIsSatisfied
}
scenario("produce message, forward and async produce failure response") {
given("a registered one-way producer configured with a forward target")
val target = actorOf[ProducingForwardTarget].start
val producer = actorOf(new TestForwarder("direct:producer-test-3", target)).start
when("a test message causing an exception is sent to the producer with !")
mockEndpoint.expectedMessageCount(1)
mockEndpoint.message(0).body().isInstanceOf(classOf[Failure])
val result = producer.!(Message("fail"))(Some(producer))
then("a failure response should have been produced by the forward target")
mockEndpoint.assertIsSatisfied
}
}
private def mockEndpoint = CamelContextManager.context.getEndpoint("mock:mock", classOf[MockEndpoint])
}
object ProducerFeatureTest {
class TestProducer(uri: String, upper: Boolean = false) extends Actor with Producer {
def endpointUri = uri
override protected def receiveBeforeProduce = {
case msg: Message => if (upper) msg.transformBody[String] { _.toUpperCase } else msg
}
}
class TestForwarder(uri: String, target: ActorRef) extends Actor with Producer {
def endpointUri = uri
override protected def receiveAfterProduce = {
case msg => target forward msg
}
}
class TestResponder extends Actor {
protected def receive = {
case msg: Message => msg.body match {
case "fail" => self.reply(Failure(new Exception("failure"), msg.headers))
case _ => self.reply(msg.transformBody[String] { "received %s" format _ })
}
}
}
class ReplyingForwardTarget extends Actor {
protected def receive = {
case msg: Message =>
self.reply(msg.addHeader("test" -> "result"))
case msg: Failure =>
self.reply(Failure(msg.cause, msg.headers + ("test" -> "failure")))
}
}
class ProducingForwardTarget extends Actor with Producer with Oneway {
def endpointUri = "direct:forward-test-1"
}
class TestRoute extends RouteBuilder {
val responder = actorOf[TestResponder].start
def configure {
from("direct:forward-test-1").to("mock:mock")
// for one-way messaging tests
from("direct:producer-test-1").to("mock:mock")
// for two-way messaging tests
// for two-way messaging tests (async)
from("direct:producer-test-3").to("actor:uuid:%s" format responder.uuid)
// for two-way messaging tests (sync)
from("direct:producer-test-2").process(new Processor() {
def process(exchange: Exchange) = {
exchange.getIn.getBody match {

View file

@ -32,28 +32,28 @@ class PublishRequestorTest extends JUnitSuite {
}
@Test def shouldReceiveConsumerMethodRegisteredEvent = {
val obj = ActiveObject.newInstance(classOf[PojoSingle])
val init = AspectInit(classOf[PojoSingle], null, None, 1000)
val obj = TypedActor.newInstance(classOf[PojoSingleIntf], classOf[PojoSingle])
val init = AspectInit(classOf[PojoSingleIntf], null, null, None, 1000)
val latch = (publisher !! SetExpectedTestMessageCount(1)).as[CountDownLatch].get
requestor ! AspectInitRegistered(obj, init)
assert(latch.await(5000, TimeUnit.MILLISECONDS))
val event = (publisher !! GetRetainedMessage).get.asInstanceOf[ConsumerMethodRegistered]
assert(event.init === init)
assert(event.uri === "direct:foo")
assert(event.activeObject === obj)
assert(event.typedActor === obj)
assert(event.method.getName === "foo")
}
@Test def shouldReceiveConsumerMethodUnregisteredEvent = {
val obj = ActiveObject.newInstance(classOf[PojoSingle])
val init = AspectInit(classOf[PojoSingle], null, None, 1000)
val obj = TypedActor.newInstance(classOf[PojoSingleIntf], classOf[PojoSingle])
val init = AspectInit(classOf[PojoSingleIntf], null, null, None, 1000)
val latch = (publisher !! SetExpectedTestMessageCount(1)).as[CountDownLatch].get
requestor ! AspectInitUnregistered(obj, init)
assert(latch.await(5000, TimeUnit.MILLISECONDS))
val event = (publisher !! GetRetainedMessage).get.asInstanceOf[ConsumerMethodUnregistered]
assert(event.init === init)
assert(event.uri === "direct:foo")
assert(event.activeObject === obj)
assert(event.typedActor === obj)
assert(event.method.getName === "foo")
}
@ -62,7 +62,7 @@ class PublishRequestorTest extends JUnitSuite {
requestor ! ActorRegistered(consumer)
assert(latch.await(5000, TimeUnit.MILLISECONDS))
assert((publisher !! GetRetainedMessage) ===
Some(ConsumerRegistered(consumer, "mock:test", consumer.uuid, true)))
Some(ConsumerRegistered(consumer, "mock:test", consumer.uuid, false)))
}
@Test def shouldReceiveConsumerUnregisteredEvent = {
@ -70,7 +70,7 @@ class PublishRequestorTest extends JUnitSuite {
requestor ! ActorUnregistered(consumer)
assert(latch.await(5000, TimeUnit.MILLISECONDS))
assert((publisher !! GetRetainedMessage) ===
Some(ConsumerUnregistered(consumer, "mock:test", consumer.uuid, true)))
Some(ConsumerUnregistered(consumer, "mock:test", consumer.uuid)))
}
}

View file

@ -5,7 +5,7 @@ import java.util.concurrent.{CountDownLatch, TimeUnit}
import org.scalatest.{GivenWhenThen, BeforeAndAfterAll, FeatureSpec}
import se.scalablesolutions.akka.actor.Actor._
import se.scalablesolutions.akka.actor.{ActiveObject, ActorRegistry, RemoteActor}
import se.scalablesolutions.akka.actor.{TypedActor, ActorRegistry, RemoteActor}
import se.scalablesolutions.akka.remote.{RemoteClient, RemoteServer}
/**
@ -20,7 +20,7 @@ class RemoteConsumerTest extends FeatureSpec with BeforeAndAfterAll with GivenWh
override protected def beforeAll = {
ActorRegistry.shutdownAll
service = CamelService.newInstance
service = CamelServiceFactory.createCamelService
service.load
server = new RemoteServer()
@ -45,7 +45,7 @@ class RemoteConsumerTest extends FeatureSpec with BeforeAndAfterAll with GivenWh
val consumer = actorOf[RemoteConsumer].start
when("remote consumer publication is triggered")
val latch = (service.consumerPublisher !! SetExpectedMessageCount(1)).as[CountDownLatch].get
var latch = service.expectEndpointActivationCount(1)
consumer !! "init"
assert(latch.await(5000, TimeUnit.MILLISECONDS))
@ -55,19 +55,19 @@ class RemoteConsumerTest extends FeatureSpec with BeforeAndAfterAll with GivenWh
}
}
feature("Client-initiated remote consumer active object") {
feature("Client-initiated remote consumer typed actor") {
scenario("access published remote consumer method") {
given("a client-initiated remote consumer active object")
val consumer = ActiveObject.newRemoteInstance(classOf[PojoRemote], host, port)
given("a client-initiated remote consumer typed actor")
val consumer = TypedActor.newRemoteInstance(classOf[PojoRemoteIntf], classOf[PojoRemote], host, port)
when("remote consumer publication is triggered")
val latch = (service.consumerPublisher !! SetExpectedMessageCount(1)).as[CountDownLatch].get
var latch = service.expectEndpointActivationCount(1)
consumer.foo("init")
assert(latch.await(5000, TimeUnit.MILLISECONDS))
then("the published method is accessible via its endpoint URI")
val response = CamelContextManager.template.requestBody("direct:remote-active-object", "test")
assert(response === "remote active object: test")
val response = CamelContextManager.template.requestBody("direct:remote-typed-actor", "test")
assert(response === "remote typed actor: test")
}
}
}

View file

@ -3,23 +3,31 @@ package se.scalablesolutions.akka.camel.component
import java.util.concurrent.{TimeUnit, CountDownLatch}
import org.apache.camel.RuntimeCamelException
import org.apache.camel.builder.RouteBuilder
import org.apache.camel.component.mock.MockEndpoint
import org.scalatest.{BeforeAndAfterEach, BeforeAndAfterAll, FeatureSpec}
import se.scalablesolutions.akka.actor.Actor._
import se.scalablesolutions.akka.actor.{ActorRegistry, Actor}
import se.scalablesolutions.akka.camel.{Message, CamelContextManager}
import se.scalablesolutions.akka.camel.{Failure, Message, CamelContextManager}
import se.scalablesolutions.akka.camel.support._
class ActorComponentFeatureTest extends FeatureSpec with BeforeAndAfterAll with BeforeAndAfterEach {
import ActorComponentFeatureTest._
override protected def beforeAll = {
ActorRegistry.shutdownAll
CamelContextManager.init
CamelContextManager.context.addRoutes(new TestRoute)
CamelContextManager.start
}
override protected def afterAll = CamelContextManager.stop
override protected def afterEach = ActorRegistry.shutdownAll
override protected def afterEach = {
ActorRegistry.shutdownAll
mockEndpoint.reset
}
feature("Communicate with an actor from a Camel application using actor endpoint URIs") {
import CamelContextManager.template
@ -55,8 +63,49 @@ class ActorComponentFeatureTest extends FeatureSpec with BeforeAndAfterAll with
scenario("two-way communication with timeout") {
val actor = actorOf[Tester3].start
intercept[RuntimeCamelException] {
template.requestBody("actor:uuid:%s" format actor.uuid, "Martin")
template.requestBody("actor:uuid:%s?blocking=true" format actor.uuid, "Martin")
}
}
scenario("two-way async communication with failure response") {
mockEndpoint.expectedBodiesReceived("whatever")
template.requestBody("direct:failure-test-1", "whatever")
mockEndpoint.assertIsSatisfied
}
scenario("two-way sync communication with exception") {
mockEndpoint.expectedBodiesReceived("whatever")
template.requestBody("direct:failure-test-2", "whatever")
mockEndpoint.assertIsSatisfied
}
}
private def mockEndpoint = CamelContextManager.context.getEndpoint("mock:mock", classOf[MockEndpoint])
}
object ActorComponentFeatureTest {
class FailWithMessage extends Actor {
protected def receive = {
case msg: Message => self.reply(Failure(new Exception("test")))
}
}
class FailWithException extends Actor {
protected def receive = {
case msg: Message => throw new Exception("test")
}
}
class TestRoute extends RouteBuilder {
val failWithMessage = actorOf[FailWithMessage].start
val failWithException = actorOf[FailWithException].start
def configure {
from("direct:failure-test-1")
.onException(classOf[Exception]).to("mock:mock").handled(true).end
.to("actor:uuid:%s" format failWithMessage.uuid)
from("direct:failure-test-2")
.onException(classOf[Exception]).to("mock:mock").handled(true).end
.to("actor:uuid:%s?blocking=true" format failWithException.uuid)
}
}
}

View file

@ -1,11 +1,12 @@
package se.scalablesolutions.akka.camel.component
import org.apache.camel.{Endpoint, AsyncProcessor}
import org.apache.camel.impl.DefaultCamelContext
import org.junit._
import org.scalatest.junit.JUnitSuite
class ActorComponentTest extends JUnitSuite {
val component: ActorComponent = ActorComponentTest.mockComponent
val component: ActorComponent = ActorComponentTest.actorComponent
@Test def shouldCreateEndpointWithIdDefined = {
val ep1: ActorEndpoint = component.createEndpoint("actor:abc").asInstanceOf[ActorEndpoint]
@ -14,21 +15,33 @@ class ActorComponentTest extends JUnitSuite {
assert(ep2.id === Some("abc"))
assert(ep1.uuid === None)
assert(ep2.uuid === None)
assert(!ep1.blocking)
assert(!ep2.blocking)
}
@Test def shouldCreateEndpointWithUuidDefined = {
val ep: ActorEndpoint = component.createEndpoint("actor:uuid:abc").asInstanceOf[ActorEndpoint]
assert(ep.uuid === Some("abc"))
assert(ep.id === None)
assert(!ep.blocking)
}
@Test def shouldCreateEndpointWithBlockingSet = {
val ep: ActorEndpoint = component.createEndpoint("actor:uuid:abc?blocking=true").asInstanceOf[ActorEndpoint]
assert(ep.uuid === Some("abc"))
assert(ep.id === None)
assert(ep.blocking)
}
}
object ActorComponentTest {
def mockComponent = {
def actorComponent = {
val component = new ActorComponent
component.setCamelContext(new DefaultCamelContext)
component
}
def mockEndpoint(uri:String) = mockComponent.createEndpoint(uri)
def actorEndpoint(uri:String) = actorComponent.createEndpoint(uri)
def actorProducer(endpoint: Endpoint) = endpoint.createProducer
def actorAsyncProducer(endpoint: Endpoint) = endpoint.createProducer.asInstanceOf[AsyncProcessor]
}

View file

@ -4,7 +4,8 @@ import ActorComponentTest._
import java.util.concurrent.{CountDownLatch, TimeoutException, TimeUnit}
import org.apache.camel.ExchangePattern
import org.apache.camel.{AsyncCallback, ExchangePattern}
import org.junit.{After, Test}
import org.scalatest.junit.JUnitSuite
import org.scalatest.BeforeAndAfterAll
@ -15,44 +16,77 @@ import se.scalablesolutions.akka.camel.{Failure, Message}
import se.scalablesolutions.akka.camel.support._
class ActorProducerTest extends JUnitSuite with BeforeAndAfterAll {
import ActorProducerTest._
@After def tearDown = ActorRegistry.shutdownAll
@Test def shouldSendMessageToActor = {
@Test def shouldSendMessageToActorWithProcessor = {
val actor = actorOf[Tester1].start
val latch = (actor !! SetExpectedMessageCount(1)).as[CountDownLatch].get
val endpoint = mockEndpoint("actor:uuid:%s" format actor.uuid)
val endpoint = actorEndpoint("actor:uuid:%s" format actor.uuid)
val exchange = endpoint.createExchange(ExchangePattern.InOnly)
exchange.getIn.setBody("Martin")
exchange.getIn.setHeader("k1", "v1")
endpoint.createProducer.process(exchange)
actorProducer(endpoint).process(exchange)
assert(latch.await(5000, TimeUnit.MILLISECONDS))
val reply = (actor !! GetRetainedMessage).get.asInstanceOf[Message]
assert(reply.body === "Martin")
assert(reply.headers === Map(Message.MessageExchangeId -> exchange.getExchangeId, "k1" -> "v1"))
}
@Test def shouldSendMessageToActorAndReceiveResponse = {
@Test def shouldSendMessageToActorWithAsyncProcessor = {
val actor = actorOf[Tester1].start
val latch = (actor !! SetExpectedMessageCount(1)).as[CountDownLatch].get
val endpoint = actorEndpoint("actor:uuid:%s" format actor.uuid)
val exchange = endpoint.createExchange(ExchangePattern.InOnly)
exchange.getIn.setBody("Martin")
exchange.getIn.setHeader("k1", "v1")
actorAsyncProducer(endpoint).process(exchange, expectSyncCompletion)
assert(latch.await(5000, TimeUnit.MILLISECONDS))
val reply = (actor !! GetRetainedMessage).get.asInstanceOf[Message]
assert(reply.body === "Martin")
assert(reply.headers === Map(Message.MessageExchangeId -> exchange.getExchangeId, "k1" -> "v1"))
}
@Test def shouldSendMessageToActorAndReceiveResponseWithProcessor = {
val actor = actorOf(new Tester2 {
override def response(msg: Message) = Message(super.response(msg), Map("k2" -> "v2"))
}).start
val endpoint = mockEndpoint("actor:uuid:%s" format actor.uuid)
val endpoint = actorEndpoint("actor:uuid:%s" format actor.uuid)
val exchange = endpoint.createExchange(ExchangePattern.InOut)
exchange.getIn.setBody("Martin")
exchange.getIn.setHeader("k1", "v1")
endpoint.createProducer.process(exchange)
actorProducer(endpoint).process(exchange)
assert(exchange.getOut.getBody === "Hello Martin")
assert(exchange.getOut.getHeader("k2") === "v2")
}
@Test def shouldSendMessageToActorAndReceiveFailure = {
@Test def shouldSendMessageToActorAndReceiveResponseWithAsyncProcessor = {
val actor = actorOf(new Tester2 {
override def response(msg: Message) = Failure(new Exception("testmsg"), Map("k3" -> "v3"))
override def response(msg: Message) = Message(super.response(msg), Map("k2" -> "v2"))
}).start
val endpoint = mockEndpoint("actor:uuid:%s" format actor.uuid)
val completion = expectAsyncCompletion
val endpoint = actorEndpoint("actor:uuid:%s" format actor.uuid)
val exchange = endpoint.createExchange(ExchangePattern.InOut)
exchange.getIn.setBody("Martin")
exchange.getIn.setHeader("k1", "v1")
endpoint.createProducer.process(exchange)
actorAsyncProducer(endpoint).process(exchange, completion)
assert(completion.latch.await(5000, TimeUnit.MILLISECONDS))
assert(exchange.getOut.getBody === "Hello Martin")
assert(exchange.getOut.getHeader("k2") === "v2")
}
@Test def shouldSendMessageToActorAndReceiveFailureWithAsyncProcessor = {
val actor = actorOf(new Tester2 {
override def response(msg: Message) = Failure(new Exception("testmsg"), Map("k3" -> "v3"))
}).start
val completion = expectAsyncCompletion
val endpoint = actorEndpoint("actor:uuid:%s" format actor.uuid)
val exchange = endpoint.createExchange(ExchangePattern.InOut)
exchange.getIn.setBody("Martin")
exchange.getIn.setHeader("k1", "v1")
actorAsyncProducer(endpoint).process(exchange, completion)
assert(completion.latch.await(5000, TimeUnit.MILLISECONDS))
assert(exchange.getException.getMessage === "testmsg")
assert(exchange.getOut.getBody === null)
assert(exchange.getOut.getHeader("k3") === null) // headers from failure message are currently ignored
@ -60,7 +94,7 @@ class ActorProducerTest extends JUnitSuite with BeforeAndAfterAll {
@Test def shouldSendMessageToActorAndTimeout(): Unit = {
val actor = actorOf[Tester3].start
val endpoint = mockEndpoint("actor:uuid:%s" format actor.uuid)
val endpoint = actorEndpoint("actor:uuid:%s" format actor.uuid)
val exchange = endpoint.createExchange(ExchangePattern.InOut)
exchange.getIn.setBody("Martin")
intercept[TimeoutException] {
@ -68,3 +102,18 @@ class ActorProducerTest extends JUnitSuite with BeforeAndAfterAll {
}
}
}
object ActorProducerTest {
def expectSyncCompletion = new AsyncCallback {
def done(doneSync: Boolean) = assert(doneSync)
}
def expectAsyncCompletion = new AsyncCallback {
val latch = new CountDownLatch(1);
def done(doneSync: Boolean) = {
assert(!doneSync)
latch.countDown
}
}
}

View file

@ -4,7 +4,7 @@ import org.scalatest.{BeforeAndAfterEach, BeforeAndAfterAll, FeatureSpec}
import org.apache.camel.builder.RouteBuilder
import se.scalablesolutions.akka.actor.Actor._
import se.scalablesolutions.akka.actor.{ActorRegistry, ActiveObject}
import se.scalablesolutions.akka.actor.{ActorRegistry, TypedActor}
import se.scalablesolutions.akka.camel._
import org.apache.camel.impl.{DefaultCamelContext, SimpleRegistry}
import org.apache.camel.{ResolveEndpointFailedException, ExchangePattern, Exchange, Processor}
@ -12,14 +12,14 @@ import org.apache.camel.{ResolveEndpointFailedException, ExchangePattern, Exchan
/**
* @author Martin Krasser
*/
class ActiveObjectComponentFeatureTest extends FeatureSpec with BeforeAndAfterAll with BeforeAndAfterEach {
import ActiveObjectComponentFeatureTest._
class TypedActorComponentFeatureTest extends FeatureSpec with BeforeAndAfterAll with BeforeAndAfterEach {
import TypedActorComponentFeatureTest._
import CamelContextManager.template
override protected def beforeAll = {
val activePojo = ActiveObject.newInstance(classOf[Pojo]) // not a consumer
val activePojoBase = ActiveObject.newInstance(classOf[PojoBase])
val activePojoIntf = ActiveObject.newInstance(classOf[PojoIntf], new PojoImpl)
val activePojo = TypedActor.newInstance(classOf[PojoNonConsumerIntf], classOf[PojoNonConsumer]) // not a consumer
val activePojoBase = TypedActor.newInstance(classOf[PojoBaseIntf], classOf[PojoBase])
val activePojoIntf = TypedActor.newInstance(classOf[PojoIntf], classOf[PojoImpl])
val registry = new SimpleRegistry
registry.put("pojo", activePojo)
@ -28,8 +28,8 @@ class ActiveObjectComponentFeatureTest extends FeatureSpec with BeforeAndAfterAl
CamelContextManager.context.addRoutes(new CustomRouteBuilder)
CamelContextManager.start
CamelContextManager.activeObjectRegistry.put("base", activePojoBase)
CamelContextManager.activeObjectRegistry.put("intf", activePojoIntf)
CamelContextManager.typedActorRegistry.put("base", activePojoBase)
CamelContextManager.typedActorRegistry.put("intf", activePojoIntf)
}
override protected def afterAll = {
@ -37,8 +37,8 @@ class ActiveObjectComponentFeatureTest extends FeatureSpec with BeforeAndAfterAl
ActorRegistry.shutdownAll
}
feature("Communicate with an active object from a Camel application using active object endpoint URIs") {
import ActiveObjectComponent.InternalSchema
feature("Communicate with an typed actor from a Camel application using typed actor endpoint URIs") {
import TypedActorComponent.InternalSchema
import ExchangePattern._
scenario("in-out exchange with proxy created from interface and method returning String") {
@ -81,25 +81,25 @@ class ActiveObjectComponentFeatureTest extends FeatureSpec with BeforeAndAfterAl
}
}
feature("Communicate with an active object from a Camel application from a custom Camel route") {
feature("Communicate with an typed actor from a Camel application from a custom Camel route") {
scenario("in-out exchange with externally registered active object") {
scenario("in-out exchange with externally registered typed actor") {
val result = template.requestBody("direct:test", "test")
assert(result === "foo: test")
}
scenario("in-out exchange with internally registered active object not possible") {
scenario("in-out exchange with internally registered typed actor not possible") {
intercept[ResolveEndpointFailedException] {
template.requestBodyAndHeader("active-object:intf?method=m2", "x", "test", "y")
template.requestBodyAndHeader("typed-actor:intf?method=m2", "x", "test", "y")
}
}
}
}
object ActiveObjectComponentFeatureTest {
object TypedActorComponentFeatureTest {
class CustomRouteBuilder extends RouteBuilder {
def configure = {
from("direct:test").to("active-object:pojo?method=foo")
from("direct:test").to("typed-actor:pojo?method=foo")
}
}
}

View file

@ -1,11 +0,0 @@
/**
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
*/
package se.scalablesolutions.akka.actor.annotation;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface configuration {}

View file

@ -1,11 +0,0 @@
/**
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
*/
package se.scalablesolutions.akka.actor.annotation;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface inittransactionalstate {}

View file

@ -1,11 +0,0 @@
/**
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
*/
package se.scalablesolutions.akka.actor.annotation;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface postrestart {}

View file

@ -1,11 +0,0 @@
/**
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
*/
package se.scalablesolutions.akka.actor.annotation;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface prerestart {}

View file

@ -1,14 +0,0 @@
/**
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
*/
package se.scalablesolutions.akka.actor.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface shutdown {}

View file

@ -1,11 +0,0 @@
/**
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
*/
package se.scalablesolutions.akka.actor.annotation;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface state {}

View file

@ -1,11 +0,0 @@
/**
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
*/
package se.scalablesolutions.akka.actor.annotation;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface transactionrequired {}

View file

@ -13,10 +13,10 @@ import com.google.inject.Singleton;
/**
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
public class ActiveObjectGuiceModule extends AbstractModule {
public class TypedActorGuiceModule extends AbstractModule {
private final List<DependencyBinding> bindings;
public ActiveObjectGuiceModule(final List<DependencyBinding> bindings) {
public TypedActorGuiceModule(final List<DependencyBinding> bindings) {
this.bindings = bindings;
}

View file

@ -36,10 +36,11 @@ message SerializedActorRefProtocol {
optional string serializerClassname = 6;
optional bool isTransactor = 7;
optional uint64 timeout = 8;
optional LifeCycleProtocol lifeCycle = 9;
optional RemoteActorRefProtocol supervisor = 10;
optional bytes hotswapStack = 11;
repeated RemoteRequestProtocol messages = 12;
optional uint64 receiveTimeout = 9;
optional LifeCycleProtocol lifeCycle = 10;
optional RemoteActorRefProtocol supervisor = 11;
optional bytes hotswapStack = 12;
repeated RemoteRequestProtocol messages = 13;
}
/**
@ -51,21 +52,35 @@ message MessageProtocol {
optional bytes messageManifest = 3;
}
/**
* Defines the actor info.
*/
message ActorInfoProtocol {
required string uuid = 1;
required string target = 2;
required uint64 timeout = 3;
required ActorType actorType = 4;
optional TypedActorInfoProtocol typedActorInfo = 5;
}
/**
* Defines the typed actor extra info.
*/
message TypedActorInfoProtocol {
required string interface = 1;
required string method = 2;
}
/**
* Defines a remote message request.
*/
message RemoteRequestProtocol {
required uint64 id = 1;
required MessageProtocol message = 2;
optional string method = 3;
required string target = 4;
required string uuid = 5;
required uint64 timeout = 6;
optional string supervisorUuid = 7;
required bool isActor = 8;
required bool isOneWay = 9;
required bool isEscaped = 10;
optional RemoteActorRefProtocol sender = 11;
required ActorInfoProtocol actorInfo = 3;
required bool isOneWay = 4;
optional string supervisorUuid = 5;
optional RemoteActorRefProtocol sender = 6;
}
/**
@ -80,6 +95,15 @@ message RemoteReplyProtocol {
required bool isSuccessful = 6;
}
/**
* Defines the actor type.
*/
enum ActorType {
SCALA_ACTOR = 1;
JAVA_ACTOR = 2;
TYPED_ACTOR = 3;
}
/**
* Defines the serialization scheme used to serialize the message and/or Actor instance.
*/
@ -114,8 +138,6 @@ enum DispatcherType {
*/
message LifeCycleProtocol {
required LifeCycleType lifeCycle = 1;
optional string preRestart = 2;
optional string postRestart = 3;
}
/**

View file

@ -1,7 +1,7 @@
<aspectwerkz>
<system id="akka">
<package name="se.scalablesolutions.akka.actor">
<aspect class="ActiveObjectAspect" />
<aspect class="TypedActorAspect" />
</package>
</system>
</aspectwerkz>

View file

@ -1,860 +0,0 @@
/**
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
*/
package se.scalablesolutions.akka.actor
import Actor._
import se.scalablesolutions.akka.config.FaultHandlingStrategy
import se.scalablesolutions.akka.remote.protocol.RemoteProtocol.RemoteRequestProtocol
import se.scalablesolutions.akka.remote.{MessageSerializer, RemoteClient, RemoteRequestProtocolIdFactory}
import se.scalablesolutions.akka.dispatch.{MessageDispatcher, Future, CompletableFuture}
import se.scalablesolutions.akka.config.ScalaConfig._
import se.scalablesolutions.akka.serialization.Serializer
import se.scalablesolutions.akka.util._
import org.codehaus.aspectwerkz.joinpoint.{MethodRtti, JoinPoint}
import org.codehaus.aspectwerkz.proxy.Proxy
import org.codehaus.aspectwerkz.annotation.{Aspect, Around}
import java.net.InetSocketAddress
import java.lang.reflect.{InvocationTargetException, Method}
object Annotations {
import se.scalablesolutions.akka.actor.annotation._
val transactionrequired = classOf[transactionrequired]
val prerestart = classOf[prerestart]
val postrestart = classOf[postrestart]
val shutdown = classOf[shutdown]
val inittransactionalstate = classOf[inittransactionalstate]
}
/**
* Configuration factory for Active Objects.
*
* FIXDOC: document ActiveObjectConfiguration
*/
final class ActiveObjectConfiguration {
private[akka] var _timeout: Long = Actor.TIMEOUT
private[akka] var _restartCallbacks: Option[RestartCallbacks] = None
private[akka] var _shutdownCallback: Option[ShutdownCallback] = None
private[akka] var _transactionRequired = false
private[akka] var _host: Option[InetSocketAddress] = None
private[akka] var _messageDispatcher: Option[MessageDispatcher] = None
def timeout(timeout: Long) : ActiveObjectConfiguration = {
_timeout = timeout
this
}
def restartCallbacks(pre: String, post: String) : ActiveObjectConfiguration = {
_restartCallbacks = Some(new RestartCallbacks(pre, post))
this
}
def shutdownCallback(down: String) : ActiveObjectConfiguration = {
_shutdownCallback = Some(new ShutdownCallback(down))
this
}
def makeTransactionRequired() : ActiveObjectConfiguration = {
_transactionRequired = true;
this
}
def makeRemote(hostname: String, port: Int) : ActiveObjectConfiguration = {
_host = Some(new InetSocketAddress(hostname, port))
this
}
def dispatcher(messageDispatcher: MessageDispatcher) : ActiveObjectConfiguration = {
_messageDispatcher = Some(messageDispatcher)
this
}
}
/**
* Holds RTTI (runtime type information) for the Active Object, f.e. current 'sender'
* reference, the 'senderFuture' reference etc.
* <p/>
* In order to make use of this context you have to create a member field in your
* Active Object that has the type 'ActiveObjectContext', then an instance will
* be injected for you to use.
* <p/>
* This class does not contain static information but is updated by the runtime system
* at runtime.
* <p/>
* Here is an example of usage:
* <pre>
* class Ping {
* // This context will be injected, holds RTTI (runtime type information)
* // for the current message send
* private ActiveObjectContext context = null;
*
* public void hit(int count) {
* Pong pong = (Pong) context.getSender();
* pong.hit(count++)
* }
* }
* </pre>
*
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
final class ActiveObjectContext {
private[akka] var _sender: AnyRef = _
private[akka] var _senderFuture: CompletableFuture[Any] = _
/**
* Returns the current sender Active Object reference.
* Scala style getter.
*/
def sender: AnyRef = {
if (_sender eq null) throw new IllegalActorStateException("Sender reference should not be null.")
else _sender
}
/**
* Returns the current sender Active Object reference.
* Java style getter.
*/
def getSender: AnyRef = {
if (_sender eq null) throw new IllegalActorStateException("Sender reference should not be null.")
else _sender
}
/**
* Returns the current sender future Active Object reference.
* Scala style getter.
*/
def senderFuture: Option[CompletableFuture[Any]] = if (_senderFuture eq null) None else Some(_senderFuture)
/**
* Returns the current sender future Active Object reference.
* Java style getter.
* This method returns 'null' if the sender future is not available.
*/
def getSenderFuture = _senderFuture
}
/**
* Internal helper class to help pass the contextual information between threads.
*
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
private[akka] object ActiveObjectContext {
import scala.util.DynamicVariable
private[actor] val sender = new DynamicVariable[AnyRef](null)
private[actor] val senderFuture = new DynamicVariable[CompletableFuture[Any]](null)
}
/**
* Factory class for creating Active Objects out of plain POJOs and/or POJOs with interfaces.
*
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
object ActiveObject extends Logging {
import Actor.actorOf
val AKKA_CAMEL_ROUTING_SCHEME = "akka"
private[actor] val AW_PROXY_PREFIX = "$$ProxiedByAW".intern
def newInstance[T](target: Class[T], timeout: Long): T =
newInstance(target, actorOf(new Dispatcher(false)), None, timeout)
def newInstance[T](target: Class[T]): T =
newInstance(target, actorOf(new Dispatcher(false)), None, Actor.TIMEOUT)
def newInstance[T](intf: Class[T], target: AnyRef, timeout: Long): T =
newInstance(intf, target, actorOf(new Dispatcher(false)), None, timeout)
def newInstance[T](intf: Class[T], target: AnyRef): T =
newInstance(intf, target, actorOf(new Dispatcher(false)), None, Actor.TIMEOUT)
def newRemoteInstance[T](target: Class[T], timeout: Long, hostname: String, port: Int): T =
newInstance(target, actorOf(new Dispatcher(false)), Some(new InetSocketAddress(hostname, port)), timeout)
def newRemoteInstance[T](target: Class[T], hostname: String, port: Int): T =
newInstance(target, actorOf(new Dispatcher(false)), Some(new InetSocketAddress(hostname, port)), Actor.TIMEOUT)
def newInstance[T](target: Class[T], config: ActiveObjectConfiguration): T = {
val actor = actorOf(new Dispatcher(config._transactionRequired, config._restartCallbacks, config._shutdownCallback))
if (config._messageDispatcher.isDefined) {
actor.dispatcher = config._messageDispatcher.get
}
newInstance(target, actor, config._host, config._timeout)
}
def newInstance[T](intf: Class[T], target: AnyRef, config: ActiveObjectConfiguration): T = {
val actor = actorOf(new Dispatcher(config._transactionRequired, config._restartCallbacks, config._shutdownCallback))
if (config._messageDispatcher.isDefined) {
actor.dispatcher = config._messageDispatcher.get
}
newInstance(intf, target, actor, config._host, config._timeout)
}
@deprecated("use newInstance(target: Class[T], config: ActiveObjectConfiguration) instead")
def newInstance[T](target: Class[T], timeout: Long, restartCallbacks: Option[RestartCallbacks]): T =
newInstance(target, actorOf(new Dispatcher(false, restartCallbacks)), None, timeout)
@deprecated("use newInstance(intf: Class[T], target: AnyRef, config: ActiveObjectConfiguration) instead")
def newInstance[T](intf: Class[T], target: AnyRef, timeout: Long, restartCallbacks: Option[RestartCallbacks]): T =
newInstance(intf, target, actorOf(new Dispatcher(false, restartCallbacks)), None, timeout)
@deprecated("use newInstance(target: Class[T], config: ActiveObjectConfiguration) instead")
def newInstance[T](target: Class[T], timeout: Long, transactionRequired: Boolean): T =
newInstance(target, actorOf(new Dispatcher(transactionRequired, None)), None, timeout)
@deprecated("use newInstance(target: Class[T], config: ActiveObjectConfiguration) instead")
def newInstance[T](target: Class[T], timeout: Long, transactionRequired: Boolean, restartCallbacks: Option[RestartCallbacks]): T =
newInstance(target, actorOf(new Dispatcher(transactionRequired, restartCallbacks)), None, timeout)
@deprecated("use newInstance(intf: Class[T], target: AnyRef, config: ActiveObjectConfiguration) instead")
def newInstance[T](intf: Class[T], target: AnyRef, timeout: Long, transactionRequired: Boolean): T =
newInstance(intf, target, actorOf(new Dispatcher(transactionRequired, None)), None, timeout)
@deprecated("use newInstance(intf: Class[T], target: AnyRef, config: ActiveObjectConfiguration) instead")
def newInstance[T](intf: Class[T], target: AnyRef, timeout: Long, transactionRequired: Boolean, restartCallbacks: Option[RestartCallbacks]): T =
newInstance(intf, target, actorOf(new Dispatcher(transactionRequired, restartCallbacks)), None, timeout)
@deprecated("use newInstance(intf: Class[T], target: AnyRef, config: ActiveObjectConfiguration) instead")
def newRemoteInstance[T](intf: Class[T], target: AnyRef, timeout: Long, hostname: String, port: Int): T =
newInstance(intf, target, actorOf(new Dispatcher(false, None)), Some(new InetSocketAddress(hostname, port)), timeout)
@deprecated("use newInstance(intf: Class[T], target: AnyRef, config: ActiveObjectConfiguration) instead")
def newRemoteInstance[T](intf: Class[T], target: AnyRef, timeout: Long, hostname: String, port: Int, restartCallbacks: Option[RestartCallbacks]): T =
newInstance(intf, target, actorOf(new Dispatcher(false, restartCallbacks)), Some(new InetSocketAddress(hostname, port)), timeout)
@deprecated("use newInstance(target: Class[T], config: ActiveObjectConfiguration) instead")
def newRemoteInstance[T](target: Class[T], timeout: Long, transactionRequired: Boolean, hostname: String, port: Int): T =
newInstance(target, actorOf(new Dispatcher(transactionRequired, None)), Some(new InetSocketAddress(hostname, port)), timeout)
@deprecated("use newInstance(target: Class[T], config: ActiveObjectConfiguration) instead")
def newRemoteInstance[T](target: Class[T], timeout: Long, transactionRequired: Boolean, hostname: String, port: Int, restartCallbacks: Option[RestartCallbacks]): T =
newInstance(target, actorOf(new Dispatcher(transactionRequired, restartCallbacks)), Some(new InetSocketAddress(hostname, port)), timeout)
@deprecated("use newInstance(intf: Class[T], target: AnyRef, config: ActiveObjectConfiguration) instead")
def newRemoteInstance[T](intf: Class[T], target: AnyRef, timeout: Long, transactionRequired: Boolean, hostname: String, port: Int): T =
newInstance(intf, target, actorOf(new Dispatcher(transactionRequired, None)), Some(new InetSocketAddress(hostname, port)), timeout)
@deprecated("use newInstance(intf: Class[T], target: AnyRef, config: ActiveObjectConfiguration) instead")
def newRemoteInstance[T](intf: Class[T], target: AnyRef, timeout: Long, transactionRequired: Boolean, hostname: String, port: Int, restartCallbacks: Option[RestartCallbacks]): T =
newInstance(intf, target, actorOf(new Dispatcher(transactionRequired, restartCallbacks)), Some(new InetSocketAddress(hostname, port)), timeout)
@deprecated("use newInstance(target: Class[T], config: ActiveObjectConfiguration) instead")
def newInstance[T](target: Class[T], timeout: Long, dispatcher: MessageDispatcher): T = {
val actor = actorOf(new Dispatcher(false, None))
actor.dispatcher = dispatcher
newInstance(target, actor, None, timeout)
}
@deprecated("use newInstance(target: Class[T], config: ActiveObjectConfiguration) instead")
def newInstance[T](target: Class[T], timeout: Long, dispatcher: MessageDispatcher, restartCallbacks: Option[RestartCallbacks]): T = {
val actor = actorOf(new Dispatcher(false, restartCallbacks))
actor.dispatcher = dispatcher
newInstance(target, actor, None, timeout)
}
@deprecated("use newInstance(intf: Class[T], target: AnyRef, config: ActiveObjectConfiguration) instead")
def newInstance[T](intf: Class[T], target: AnyRef, timeout: Long, dispatcher: MessageDispatcher): T = {
val actor = actorOf(new Dispatcher(false, None))
actor.dispatcher = dispatcher
newInstance(intf, target, actor, None, timeout)
}
@deprecated("use newInstance(intf: Class[T], target: AnyRef, config: ActiveObjectConfiguration) instead")
def newInstance[T](intf: Class[T], target: AnyRef, timeout: Long,
dispatcher: MessageDispatcher, restartCallbacks: Option[RestartCallbacks]): T = {
val actor = actorOf(new Dispatcher(false, restartCallbacks))
actor.dispatcher = dispatcher
newInstance(intf, target, actor, None, timeout)
}
@deprecated("use newInstance(target: Class[T], config: ActiveObjectConfiguration) instead")
def newInstance[T](target: Class[T], timeout: Long, transactionRequired: Boolean, dispatcher: MessageDispatcher): T = {
val actor = actorOf(new Dispatcher(transactionRequired, None))
actor.dispatcher = dispatcher
newInstance(target, actor, None, timeout)
}
@deprecated("use newInstance(target: Class[T], config: ActiveObjectConfiguration) instead")
def newInstance[T](target: Class[T], timeout: Long, transactionRequired: Boolean,
dispatcher: MessageDispatcher, restartCallbacks: Option[RestartCallbacks]): T = {
val actor = actorOf(new Dispatcher(transactionRequired, restartCallbacks))
actor.dispatcher = dispatcher
newInstance(target, actor, None, timeout)
}
@deprecated("use newInstance(intf: Class[T], target: AnyRef, config: ActiveObjectConfiguration) instead")
def newInstance[T](intf: Class[T], target: AnyRef, timeout: Long, transactionRequired: Boolean, dispatcher: MessageDispatcher): T = {
val actor = actorOf(new Dispatcher(transactionRequired, None))
actor.dispatcher = dispatcher
newInstance(intf, target, actor, None, timeout)
}
@deprecated("use newInstance(intf: Class[T], target: AnyRef, config: ActiveObjectConfiguration) instead")
def newInstance[T](intf: Class[T], target: AnyRef, timeout: Long, transactionRequired: Boolean,
dispatcher: MessageDispatcher, restartCallbacks: Option[RestartCallbacks]): T = {
val actor = actorOf(new Dispatcher(transactionRequired, restartCallbacks))
actor.dispatcher = dispatcher
newInstance(intf, target, actor, None, timeout)
}
@deprecated("use newInstance(target: Class[T], config: ActiveObjectConfiguration) instead")
def newRemoteInstance[T](target: Class[T], timeout: Long, dispatcher: MessageDispatcher, hostname: String, port: Int): T = {
val actor = actorOf(new Dispatcher(false, None))
actor.dispatcher = dispatcher
newInstance(target, actor, Some(new InetSocketAddress(hostname, port)), timeout)
}
@deprecated("use newInstance(target: Class[T], config: ActiveObjectConfiguration) instead")
def newRemoteInstance[T](target: Class[T], timeout: Long, dispatcher: MessageDispatcher,
hostname: String, port: Int, restartCallbacks: Option[RestartCallbacks]): T = {
val actor = actorOf(new Dispatcher(false, restartCallbacks))
actor.dispatcher = dispatcher
newInstance(target, actor, Some(new InetSocketAddress(hostname, port)), timeout)
}
@deprecated("use newInstance(intf: Class[T], target: AnyRef, config: ActiveObjectConfiguration) instead")
def newRemoteInstance[T](intf: Class[T], target: AnyRef, timeout: Long, dispatcher: MessageDispatcher, hostname: String, port: Int): T = {
val actor = actorOf(new Dispatcher(false, None))
actor.dispatcher = dispatcher
newInstance(intf, target, actor, Some(new InetSocketAddress(hostname, port)), timeout)
}
@deprecated("use newInstance(intf: Class[T], target: AnyRef, config: ActiveObjectConfiguration) instead")
def newRemoteInstance[T](intf: Class[T], target: AnyRef, timeout: Long, dispatcher: MessageDispatcher,
hostname: String, port: Int, restartCallbacks: Option[RestartCallbacks]): T = {
val actor = actorOf(new Dispatcher(false, restartCallbacks))
actor.dispatcher = dispatcher
newInstance(intf, target, actor, Some(new InetSocketAddress(hostname, port)), timeout)
}
@deprecated("use newInstance(target: Class[T], config: ActiveObjectConfiguration) instead")
def newRemoteInstance[T](target: Class[T], timeout: Long, transactionRequired: Boolean,
dispatcher: MessageDispatcher, hostname: String, port: Int): T = {
val actor = actorOf(new Dispatcher(transactionRequired, None))
actor.dispatcher = dispatcher
newInstance(target, actor, Some(new InetSocketAddress(hostname, port)), timeout)
}
@deprecated("use newInstance(target: Class[T], config: ActiveObjectConfiguration) instead")
def newRemoteInstance[T](target: Class[T], timeout: Long, transactionRequired: Boolean, dispatcher: MessageDispatcher,
hostname: String, port: Int, restartCallbacks: Option[RestartCallbacks]): T = {
val actor = actorOf(new Dispatcher(transactionRequired, restartCallbacks))
actor.dispatcher = dispatcher
newInstance(target, actor, Some(new InetSocketAddress(hostname, port)), timeout)
}
@deprecated("use newInstance(intf: Class[T], target: AnyRef, config: ActiveObjectConfiguration) instead")
def newRemoteInstance[T](intf: Class[T], target: AnyRef, timeout: Long, transactionRequired: Boolean,
dispatcher: MessageDispatcher, hostname: String, port: Int): T = {
val actor = actorOf(new Dispatcher(transactionRequired, None))
actor.dispatcher = dispatcher
newInstance(intf, target, actor, Some(new InetSocketAddress(hostname, port)), timeout)
}
@deprecated("use newInstance(intf: Class[T], target: AnyRef, config: ActiveObjectConfiguration) instead")
def newRemoteInstance[T](intf: Class[T], target: AnyRef, timeout: Long, transactionRequired: Boolean,
dispatcher: MessageDispatcher, hostname: String, port: Int, restartCallbacks: Option[RestartCallbacks]): T = {
val actor = actorOf(new Dispatcher(transactionRequired, restartCallbacks))
actor.dispatcher = dispatcher
newInstance(intf, target, actor, Some(new InetSocketAddress(hostname, port)), timeout)
}
private[akka] def newInstance[T](target: Class[T], actorRef: ActorRef, remoteAddress: Option[InetSocketAddress], timeout: Long): T = {
val proxy = Proxy.newInstance(target, true, false)
val context = injectActiveObjectContext(proxy)
actorRef.actor.asInstanceOf[Dispatcher].initialize(target, proxy, context)
actorRef.timeout = timeout
if (remoteAddress.isDefined) actorRef.makeRemote(remoteAddress.get)
AspectInitRegistry.register(proxy, AspectInit(target, actorRef, remoteAddress, timeout))
actorRef.start
proxy.asInstanceOf[T]
}
private[akka] def newInstance[T](intf: Class[T], target: AnyRef, actorRef: ActorRef,
remoteAddress: Option[InetSocketAddress], timeout: Long): T = {
val context = injectActiveObjectContext(target)
val proxy = Proxy.newInstance(Array(intf), Array(target), true, false)
actorRef.actor.asInstanceOf[Dispatcher].initialize(target.getClass, target, context)
actorRef.timeout = timeout
if (remoteAddress.isDefined) actorRef.makeRemote(remoteAddress.get)
AspectInitRegistry.register(proxy, AspectInit(intf, actorRef, remoteAddress, timeout))
actorRef.start
proxy.asInstanceOf[T]
}
def stop(obj: AnyRef): Unit = {
val init = AspectInitRegistry.initFor(obj)
init.actorRef.stop
}
/**
* Get the underlying dispatcher actor for the given active object.
*/
def actorFor(obj: AnyRef): Option[ActorRef] =
ActorRegistry.actorsFor(classOf[Dispatcher]).find(a => a.actor.asInstanceOf[Dispatcher].target == Some(obj))
/**
* Links an other active object to this active object.
* @param supervisor the supervisor active object
* @param supervised the active object to link
*/
def link(supervisor: AnyRef, supervised: AnyRef) = {
val supervisorActor = actorFor(supervisor).getOrElse(
throw new IllegalActorStateException("Can't link when the supervisor is not an active object"))
val supervisedActor = actorFor(supervised).getOrElse(
throw new IllegalActorStateException("Can't link when the supervised is not an active object"))
supervisorActor.link(supervisedActor)
}
/**
* Links an other active object to this active object and sets the fault handling for the supervisor.
* @param supervisor the supervisor active object
* @param supervised the active object to link
* @param handler fault handling strategy
* @param trapExceptions array of exceptions that should be handled by the supervisor
*/
def link(supervisor: AnyRef, supervised: AnyRef, handler: FaultHandlingStrategy, trapExceptions: Array[Class[_ <: Throwable]]) = {
val supervisorActor = actorFor(supervisor).getOrElse(
throw new IllegalActorStateException("Can't link when the supervisor is not an active object"))
val supervisedActor = actorFor(supervised).getOrElse(
throw new IllegalActorStateException("Can't link when the supervised is not an active object"))
supervisorActor.trapExit = trapExceptions.toList
supervisorActor.faultHandler = Some(handler)
supervisorActor.link(supervisedActor)
}
/**
* Unlink the supervised active object from the supervisor.
* @param supervisor the supervisor active object
* @param supervised the active object to unlink
*/
def unlink(supervisor: AnyRef, supervised: AnyRef) = {
val supervisorActor = actorFor(supervisor).getOrElse(
throw new IllegalActorStateException("Can't unlink when the supervisor is not an active object"))
val supervisedActor = actorFor(supervised).getOrElse(
throw new IllegalActorStateException("Can't unlink when the supervised is not an active object"))
supervisorActor.unlink(supervisedActor)
}
/**
* Sets the trap exit for the given supervisor active object.
* @param supervisor the supervisor active object
* @param trapExceptions array of exceptions that should be handled by the supervisor
*/
def trapExit(supervisor: AnyRef, trapExceptions: Array[Class[_ <: Throwable]]) = {
val supervisorActor = actorFor(supervisor).getOrElse(
throw new IllegalActorStateException("Can't set trap exceptions when the supervisor is not an active object"))
supervisorActor.trapExit = trapExceptions.toList
this
}
/**
* Sets the fault handling strategy for the given supervisor active object.
* @param supervisor the supervisor active object
* @param handler fault handling strategy
*/
def faultHandler(supervisor: AnyRef, handler: FaultHandlingStrategy) = {
val supervisorActor = actorFor(supervisor).getOrElse(
throw new IllegalActorStateException("Can't set fault handler when the supervisor is not an active object"))
supervisorActor.faultHandler = Some(handler)
this
}
private def injectActiveObjectContext(activeObject: AnyRef): Option[ActiveObjectContext] = {
def injectActiveObjectContext0(activeObject: AnyRef, clazz: Class[_]): Option[ActiveObjectContext] = {
val contextField = clazz.getDeclaredFields.toList.find(_.getType == classOf[ActiveObjectContext])
if (contextField.isDefined) {
contextField.get.setAccessible(true)
val context = new ActiveObjectContext
contextField.get.set(activeObject, context)
Some(context)
} else {
val parent = clazz.getSuperclass
if (parent != null) injectActiveObjectContext0(activeObject, parent)
else {
log.trace(
"Can't set 'ActiveObjectContext' for ActiveObject [%s] since no field of this type could be found.",
activeObject.getClass.getName)
None
}
}
}
injectActiveObjectContext0(activeObject, activeObject.getClass)
}
private[akka] def supervise(restartStrategy: RestartStrategy, components: List[Supervise]): Supervisor =
Supervisor(SupervisorConfig(restartStrategy, components))
}
private[akka] object AspectInitRegistry extends ListenerManagement {
private val initializations = new java.util.concurrent.ConcurrentHashMap[AnyRef, AspectInit]
def initFor(target: AnyRef) = {
initializations.get(target)
}
def register(target: AnyRef, init: AspectInit) = {
val res = initializations.put(target, init)
foreachListener(_ ! AspectInitRegistered(target, init))
res
}
def unregister(target: AnyRef) = {
val res = initializations.remove(target)
foreachListener(_ ! AspectInitUnregistered(target, res))
res
}
}
private[akka] sealed trait AspectInitRegistryEvent
private[akka] case class AspectInitRegistered(proxy: AnyRef, init: AspectInit) extends AspectInitRegistryEvent
private[akka] case class AspectInitUnregistered(proxy: AnyRef, init: AspectInit) extends AspectInitRegistryEvent
private[akka] sealed case class AspectInit(
val target: Class[_],
val actorRef: ActorRef,
val remoteAddress: Option[InetSocketAddress],
val timeout: Long) {
def this(target: Class[_], actorRef: ActorRef, timeout: Long) = this(target, actorRef, None, timeout)
}
/**
* AspectWerkz Aspect that is turning POJOs into Active Object.
* Is deployed on a 'per-instance' basis.
*
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
@Aspect("perInstance")
private[akka] sealed class ActiveObjectAspect {
@volatile private var isInitialized = false
@volatile private var isStopped = false
private var target: Class[_] = _
private var actorRef: ActorRef = _
private var remoteAddress: Option[InetSocketAddress] = _
private var timeout: Long = _
@volatile private var instance: AnyRef = _
@Around("execution(* *.*(..))")
def invoke(joinPoint: JoinPoint): AnyRef = {
if (!isInitialized) {
val init = AspectInitRegistry.initFor(joinPoint.getThis)
target = init.target
actorRef = init.actorRef
remoteAddress = init.remoteAddress
timeout = init.timeout
isInitialized = true
}
dispatch(joinPoint)
}
private def dispatch(joinPoint: JoinPoint) = {
if (remoteAddress.isDefined) remoteDispatch(joinPoint)
else localDispatch(joinPoint)
}
private def localDispatch(joinPoint: JoinPoint): AnyRef = {
val rtti = joinPoint.getRtti.asInstanceOf[MethodRtti]
val isOneWay = isVoid(rtti)
val sender = ActiveObjectContext.sender.value
val senderFuture = ActiveObjectContext.senderFuture.value
if (!actorRef.isRunning && !isStopped) {
isStopped = true
joinPoint.proceed
} else if (isOneWay) {
actorRef ! Invocation(joinPoint, true, true, sender, senderFuture)
null.asInstanceOf[AnyRef]
} else {
val result = (actorRef !! (Invocation(joinPoint, false, isOneWay, sender, senderFuture), timeout)).as[AnyRef]
if (result.isDefined) result.get
else throw new IllegalActorStateException("No result defined for invocation [" + joinPoint + "]")
}
}
private def remoteDispatch(joinPoint: JoinPoint): AnyRef = {
val rtti = joinPoint.getRtti.asInstanceOf[MethodRtti]
val isOneWay = isVoid(rtti)
val (message: Array[AnyRef], isEscaped) = escapeArguments(rtti.getParameterValues)
val requestBuilder = RemoteRequestProtocol.newBuilder
.setId(RemoteRequestProtocolIdFactory.nextId)
.setMessage(MessageSerializer.serialize(message))
.setMethod(rtti.getMethod.getName)
.setTarget(target.getName)
.setUuid(actorRef.uuid)
.setTimeout(timeout)
.setIsActor(false)
.setIsOneWay(isOneWay)
.setIsEscaped(false)
val id = actorRef.registerSupervisorAsRemoteActor
if (id.isDefined) requestBuilder.setSupervisorUuid(id.get)
val remoteMessage = requestBuilder.build
val future = RemoteClient.clientFor(remoteAddress.get).send(remoteMessage, None)
if (isOneWay) null // for void methods
else {
if (future.isDefined) {
future.get.await
val result = getResultOrThrowException(future.get)
if (result.isDefined) result.get
else throw new IllegalActorStateException("No result returned from call to [" + joinPoint + "]")
} else throw new IllegalActorStateException("No future returned from call to [" + joinPoint + "]")
}
}
private def getResultOrThrowException[T](future: Future[T]): Option[T] =
if (future.exception.isDefined) {
val (_, cause) = future.exception.get
throw cause
} else future.result
private def isVoid(rtti: MethodRtti) = rtti.getMethod.getReturnType == java.lang.Void.TYPE
private def escapeArguments(args: Array[AnyRef]): Tuple2[Array[AnyRef], Boolean] = {
var isEscaped = false
val escapedArgs = for (arg <- args) yield {
val clazz = arg.getClass
if (clazz.getName.contains(ActiveObject.AW_PROXY_PREFIX)) {
isEscaped = true
ActiveObject.AW_PROXY_PREFIX + clazz.getSuperclass.getName
} else arg
}
(escapedArgs, isEscaped)
}
}
/**
* Represents a snapshot of the current invocation.
*
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
@serializable private[akka] case class Invocation(
joinPoint: JoinPoint, isOneWay: Boolean, isVoid: Boolean, sender: AnyRef, senderFuture: CompletableFuture[Any]) {
override def toString: String = synchronized {
"Invocation [joinPoint: " + joinPoint.toString +
", isOneWay: " + isOneWay +
", isVoid: " + isVoid +
", sender: " + sender +
", senderFuture: " + senderFuture +
"]"
}
override def hashCode: Int = synchronized {
var result = HashCode.SEED
result = HashCode.hash(result, joinPoint)
result = HashCode.hash(result, isOneWay)
result = HashCode.hash(result, isVoid)
result = HashCode.hash(result, sender)
result = HashCode.hash(result, senderFuture)
result
}
override def equals(that: Any): Boolean = synchronized {
that != null &&
that.isInstanceOf[Invocation] &&
that.asInstanceOf[Invocation].joinPoint == joinPoint &&
that.asInstanceOf[Invocation].isOneWay == isOneWay &&
that.asInstanceOf[Invocation].isVoid == isVoid &&
that.asInstanceOf[Invocation].sender == sender &&
that.asInstanceOf[Invocation].senderFuture == senderFuture
}
}
object Dispatcher {
val ZERO_ITEM_CLASS_ARRAY = Array[Class[_]]()
val ZERO_ITEM_OBJECT_ARRAY = Array[Object]()
var crashedActorTl:ThreadLocal[Dispatcher] = new ThreadLocal();
}
/**
* Generic Actor managing Invocation dispatch, transaction and error management.
*
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
private[akka] class Dispatcher(transactionalRequired: Boolean,
var restartCallbacks: Option[RestartCallbacks] = None,
var shutdownCallback: Option[ShutdownCallback] = None) extends Actor {
import Dispatcher._
private[actor] var target: Option[AnyRef] = None
private var zhutdown: Option[Method] = None
private var preRestart: Option[Method] = None
private var postRestart: Option[Method] = None
private var initTxState: Option[Method] = None
private var context: Option[ActiveObjectContext] = None
private var targetClass:Class[_] = _
def this(transactionalRequired: Boolean) = this(transactionalRequired,None)
private[actor] def initialize(targetClass: Class[_], targetInstance: AnyRef, ctx: Option[ActiveObjectContext]) = {
if (transactionalRequired || targetClass.isAnnotationPresent(Annotations.transactionrequired))
self.makeTransactionRequired
self.id = targetClass.getName
this.targetClass = targetClass
target = Some(targetInstance)
context = ctx
val methods = targetInstance.getClass.getDeclaredMethods.toList
// See if we have any config define restart callbacks
restartCallbacks match {
case None => {}
case Some(RestartCallbacks(pre, post)) =>
preRestart = Some(try {
targetInstance.getClass.getDeclaredMethod(pre, ZERO_ITEM_CLASS_ARRAY: _*)
} catch { case e => throw new IllegalActorStateException(
"Could not find pre restart method [" + pre + "] \nin [" +
targetClass.getName + "]. \nIt must have a zero argument definition.") })
postRestart = Some(try {
targetInstance.getClass.getDeclaredMethod(post, ZERO_ITEM_CLASS_ARRAY: _*)
} catch { case e => throw new IllegalActorStateException(
"Could not find post restart method [" + post + "] \nin [" +
targetClass.getName + "]. \nIt must have a zero argument definition.") })
}
// See if we have any config define a shutdown callback
shutdownCallback match {
case None => {}
case Some(ShutdownCallback(down)) =>
zhutdown = Some(try {
targetInstance.getClass.getDeclaredMethod(down, ZERO_ITEM_CLASS_ARRAY: _*)
} catch { case e => throw new IllegalStateException(
"Could not find shutdown method [" + down + "] \nin [" +
targetClass.getName + "]. \nIt must have a zero argument definition.") })
}
// See if we have any annotation defined restart callbacks
if (!preRestart.isDefined) preRestart = methods.find(m => m.isAnnotationPresent(Annotations.prerestart))
if (!postRestart.isDefined) postRestart = methods.find(m => m.isAnnotationPresent(Annotations.postrestart))
// See if we have an annotation defined shutdown callback
if (!zhutdown.isDefined) zhutdown = methods.find(m => m.isAnnotationPresent(Annotations.shutdown))
if (preRestart.isDefined && preRestart.get.getParameterTypes.length != 0)
throw new IllegalActorStateException(
"Method annotated with @prerestart or defined as a restart callback in \n[" +
targetClass.getName + "] must have a zero argument definition")
if (postRestart.isDefined && postRestart.get.getParameterTypes.length != 0)
throw new IllegalActorStateException(
"Method annotated with @postrestart or defined as a restart callback in \n[" +
targetClass.getName + "] must have a zero argument definition")
if (zhutdown.isDefined && zhutdown.get.getParameterTypes.length != 0)
throw new IllegalStateException(
"Method annotated with @shutdown or defined as a shutdown callback in \n[" +
targetClass.getName + "] must have a zero argument definition")
if (preRestart.isDefined) preRestart.get.setAccessible(true)
if (postRestart.isDefined) postRestart.get.setAccessible(true)
if (zhutdown.isDefined) zhutdown.get.setAccessible(true)
// see if we have a method annotated with @inittransactionalstate, if so invoke it
initTxState = methods.find(m => m.isAnnotationPresent(Annotations.inittransactionalstate))
if (initTxState.isDefined && initTxState.get.getParameterTypes.length != 0)
throw new IllegalActorStateException("Method annotated with @inittransactionalstate must have a zero argument definition")
if (initTxState.isDefined) initTxState.get.setAccessible(true)
}
def receive = {
case Invocation(joinPoint, isOneWay, _, sender, senderFuture) =>
context.foreach { ctx =>
if (sender ne null) ctx._sender = sender
if (senderFuture ne null) ctx._senderFuture = senderFuture
}
ActiveObjectContext.sender.value = joinPoint.getThis // set next sender
self.senderFuture.foreach(ActiveObjectContext.senderFuture.value = _)
if (Actor.SERIALIZE_MESSAGES) serializeArguments(joinPoint)
if (isOneWay) joinPoint.proceed
else self.reply(joinPoint.proceed)
// Jan Kronquist: started work on issue 121
case Link(target) => self.link(target)
case Unlink(target) => self.unlink(target)
case unexpected =>
throw new IllegalActorStateException("Unexpected message [" + unexpected + "] sent to [" + this + "]")
}
override def preRestart(reason: Throwable) {
try {
// Since preRestart is called we know that this dispatcher
// is about to be restarted. Put the instance in a thread
// local so the new dispatcher can be initialized with the contents of the
// old.
//FIXME - This should be considered as a workaround.
crashedActorTl.set(this)
if (preRestart.isDefined) preRestart.get.invoke(target.get, ZERO_ITEM_OBJECT_ARRAY: _*)
} catch { case e: InvocationTargetException => throw e.getCause }
}
override def postRestart(reason: Throwable) {
try {
if (postRestart.isDefined) {
postRestart.get.invoke(target.get, ZERO_ITEM_OBJECT_ARRAY: _*)
}
} catch { case e: InvocationTargetException => throw e.getCause }
}
override def init = {
// Get the crashed dispatcher from thread local and intitialize this actor with the
// contents of the old dispatcher
val oldActor = crashedActorTl.get();
if(oldActor != null) {
initialize(oldActor.targetClass,oldActor.target.get,oldActor.context)
crashedActorTl.set(null)
}
}
override def shutdown = {
try {
if (zhutdown.isDefined) {
zhutdown.get.invoke(target.get, ZERO_ITEM_OBJECT_ARRAY: _*)
}
} catch {
case e: InvocationTargetException => throw e.getCause
} finally {
AspectInitRegistry.unregister(target.get);
}
}
override def initTransactionalState = {
try {
if (initTxState.isDefined && target.isDefined) initTxState.get.invoke(target.get, ZERO_ITEM_OBJECT_ARRAY: _*)
} catch { case e: InvocationTargetException => throw e.getCause }
}
private def serializeArguments(joinPoint: JoinPoint) = {
val args = joinPoint.getRtti.asInstanceOf[MethodRtti].getParameterValues
var unserializable = false
var hasMutableArgument = false
for (arg <- args.toList) {
if (!arg.isInstanceOf[String] &&
!arg.isInstanceOf[Byte] &&
!arg.isInstanceOf[Int] &&
!arg.isInstanceOf[Long] &&
!arg.isInstanceOf[Float] &&
!arg.isInstanceOf[Double] &&
!arg.isInstanceOf[Boolean] &&
!arg.isInstanceOf[Char] &&
!arg.isInstanceOf[java.lang.Byte] &&
!arg.isInstanceOf[java.lang.Integer] &&
!arg.isInstanceOf[java.lang.Long] &&
!arg.isInstanceOf[java.lang.Float] &&
!arg.isInstanceOf[java.lang.Double] &&
!arg.isInstanceOf[java.lang.Boolean] &&
!arg.isInstanceOf[java.lang.Character]) {
hasMutableArgument = true
}
if (arg.getClass.getName.contains(ActiveObject.AW_PROXY_PREFIX)) unserializable = true
}
if (!unserializable && hasMutableArgument) {
val copyOfArgs = Serializer.Java.deepClone(args)
joinPoint.getRtti.asInstanceOf[MethodRtti].setParameterValues(copyOfArgs.asInstanceOf[Array[AnyRef]])
}
}
}

View file

@ -9,10 +9,12 @@ import se.scalablesolutions.akka.config.Config._
import se.scalablesolutions.akka.config.ScalaConfig._
import se.scalablesolutions.akka.serialization.Serializer
import se.scalablesolutions.akka.util.Helpers.{narrow, narrowSilently}
import se.scalablesolutions.akka.util.Logging
import se.scalablesolutions.akka.util.{Logging, Duration}
import com.google.protobuf.Message
import java.util.concurrent.TimeUnit
import java.net.InetSocketAddress
/**
* Implements the Transactor abstraction. E.g. a transactional actor.
@ -32,8 +34,9 @@ trait Transactor extends Actor {
*
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
abstract class RemoteActor(hostname: String, port: Int) extends Actor {
self.makeRemote(hostname, port)
abstract class RemoteActor(address: InetSocketAddress) extends Actor {
def this(hostname: String, port: Int) = this(new InetSocketAddress(hostname, port))
self.makeRemote(address)
}
/**
@ -46,14 +49,16 @@ case class Exit(dead: ActorRef, killer: Throwable) extends LifeCycleMessage
case class Link(child: ActorRef) extends LifeCycleMessage
case class Unlink(child: ActorRef) extends LifeCycleMessage
case class UnlinkAndStop(child: ActorRef) extends LifeCycleMessage
case object Kill extends LifeCycleMessage
case object ReceiveTimeout extends LifeCycleMessage
case class MaximumNumberOfRestartsWithinTimeRangeReached(
victim: ActorRef, maxNrOfRetries: Int, withinTimeRange: Int, lastExceptionCausingRestart: Throwable) extends LifeCycleMessage
// Exceptions for Actors
class ActorStartException private[akka](message: String) extends RuntimeException(message)
class IllegalActorStateException private[akka](message: String) extends RuntimeException(message)
class ActorKilledException private[akka](message: String) extends RuntimeException(message)
class ActorInitializationException private[akka](message: String) extends RuntimeException(message)
class ActorTimeoutException private[akka](message: String) extends RuntimeException(message)
/**
* Actor factory module with factory methods for creating various kinds of Actors.
@ -61,7 +66,7 @@ class ActorInitializationException private[akka](message: String) extends Runtim
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
object Actor extends Logging {
val TIMEOUT = config.getInt("akka.actor.timeout", 5000)
val TIMEOUT = Duration(config.getInt("akka.actor.timeout", 5), TIME_UNIT).toMillis
val SERIALIZE_MESSAGES = config.getBool("akka.actor.serialize-messages", false)
/**
@ -71,9 +76,9 @@ object Actor extends Logging {
type Receive = PartialFunction[Any, Unit]
private[actor] val actorRefInCreation = new scala.util.DynamicVariable[Option[ActorRef]](None)
/**
* Creates a Actor.actorOf out of the Actor with type T.
* Creates an ActorRef out of the Actor with type T.
* <pre>
* import Actor._
* val actor = actorOf[MyActor]
@ -89,7 +94,7 @@ object Actor extends Logging {
def actorOf[T <: Actor : Manifest]: ActorRef = new LocalActorRef(manifest[T].erasure.asInstanceOf[Class[_ <: Actor]])
/**
* Creates a Actor.actorOf out of the Actor. Allows you to pass in a factory function
* Creates an ActorRef out of the Actor. Allows you to pass in a factory function
* that creates the Actor. Please note that this function can be invoked multiple
* times if for example the Actor is supervised and needs to be restarted.
* <p/>
@ -284,6 +289,7 @@ object Actor extends Logging {
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
trait Actor extends Logging {
/**
* Type alias because traits cannot have companion objects.
*/
@ -300,12 +306,12 @@ trait Actor extends Logging {
Actor.actorRefInCreation.value = None
if (ref.isEmpty) throw new ActorInitializationException(
"ActorRef for instance of actor [" + getClass.getName + "] is not in scope." +
"\n\tYou can not create an instance of an actor explicitly using 'new MyActor'." +
"\n\tYou have to use one of the factory methods in the 'Actor' object to create a new actor." +
"\n\tEither use:" +
"\n\t\t'val actor = Actor.actorOf[MyActor]', or" +
"\n\t\t'val actor = Actor.actorOf(new MyActor(..))', or" +
"\n\t\t'val actor = Actor.actor { case msg => .. } }'")
"\n\tYou can not create an instance of an actor explicitly using 'new MyActor'." +
"\n\tYou have to use one of the factory methods in the 'Actor' object to create a new actor." +
"\n\tEither use:" +
"\n\t\t'val actor = Actor.actorOf[MyActor]', or" +
"\n\t\t'val actor = Actor.actorOf(new MyActor(..))', or" +
"\n\t\t'val actor = Actor.actor { case msg => .. } }'")
else ref
}
@ -413,6 +419,14 @@ trait Actor extends Logging {
*/
def isDefinedAt(message: Any): Boolean = base.isDefinedAt(message)
/** One of the fundamental methods of the ActorsModel
* Actor assumes a new behavior
*/
def become(behavior: Option[Receive]) {
self.hotswap = behavior
self.checkReceiveTimeout // FIXME : how to reschedule receivetimeout on hotswap?
}
// =========================================
// ==== INTERNAL IMPLEMENTATION DETAILS ====
// =========================================
@ -425,13 +439,12 @@ trait Actor extends Logging {
}
private val lifeCycles: Receive = {
case HotSwap(code) => self.hotswap = code; self.checkReceiveTimeout // FIXME : how to reschedule receivetimeout on hotswap?
case Restart(reason) => self.restart(reason)
case Exit(dead, reason) => self.handleTrapExit(dead, reason)
case Link(child) => self.link(child)
case Unlink(child) => self.unlink(child)
case HotSwap(code) => become(code)
case Exit(dead, reason) => self.handleTrapExit(dead, reason)
case Link(child) => self.link(child)
case Unlink(child) => self.unlink(child)
case UnlinkAndStop(child) => self.unlink(child); child.stop
case Kill => throw new ActorKilledException("Actor [" + toString + "] was killed by a Kill message")
case Restart(reason) => throw reason
}
}

View file

@ -10,26 +10,27 @@ import se.scalablesolutions.akka.config.{AllForOneStrategy, OneForOneStrategy, F
import se.scalablesolutions.akka.config.ScalaConfig._
import se.scalablesolutions.akka.stm.global._
import se.scalablesolutions.akka.stm.TransactionManagement._
import se.scalablesolutions.akka.stm.TransactionManagement
import se.scalablesolutions.akka.stm.{TransactionManagement, TransactionSetAbortedException}
import se.scalablesolutions.akka.remote.protocol.RemoteProtocol._
import se.scalablesolutions.akka.remote.{RemoteNode, RemoteServer, RemoteClient, MessageSerializer, RemoteRequestProtocolIdFactory}
import se.scalablesolutions.akka.serialization.Serializer
import se.scalablesolutions.akka.util.{HashCode, Logging, UUID, ReentrantGuard}
import RemoteActorSerialization._
import org.multiverse.api.ThreadLocalTransaction._
import org.multiverse.commitbarriers.CountDownCommitBarrier
import jsr166x.{Deque, ConcurrentLinkedDeque}
import org.multiverse.api.exceptions.DeadTransactionException
import java.net.InetSocketAddress
import java.util.concurrent.locks.ReentrantLock
import java.util.concurrent.atomic.AtomicReference
import java.util.concurrent.{ConcurrentHashMap, TimeUnit}
import java.util.{Map => JMap}
import java.lang.reflect.Field
import RemoteActorSerialization._
import jsr166x.{Deque, ConcurrentLinkedDeque}
import com.google.protobuf.ByteString
import java.util.concurrent.{ConcurrentHashMap, TimeUnit}
/**
* ActorRef is an immutable and serializable handle to an Actor.
@ -63,7 +64,7 @@ import java.util.concurrent.{ConcurrentHashMap, TimeUnit}
*
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
trait ActorRef extends TransactionManagement {
trait ActorRef extends TransactionManagement with java.lang.Comparable[ActorRef] {
// Only mutable for RemoteServer in order to maintain identity across nodes
@volatile protected[akka] var _uuid = UUID.newUuid.toString
@ -71,9 +72,7 @@ trait ActorRef extends TransactionManagement {
@volatile protected[this] var _isShutDown = false
@volatile protected[akka] var _isBeingRestarted = false
@volatile protected[akka] var _homeAddress = new InetSocketAddress(RemoteServer.HOSTNAME, RemoteServer.PORT)
@volatile protected[akka] var _timeoutActor: Option[ActorRef] = None
@volatile protected[akka] var startOnCreation = false
@volatile protected[akka] var registeredInRemoteNodeDuringSerialization = false
protected[this] val guard = new ReentrantGuard
@ -99,12 +98,12 @@ trait ActorRef extends TransactionManagement {
@volatile var timeout: Long = Actor.TIMEOUT
/**
* User overridable callback/setting.
* <p/>
* Defines the default timeout for an initial receive invocation.
* When specified, the receive function should be able to handle a 'ReceiveTimeout' message.
*/
@volatile var receiveTimeout: Option[Long] = None
* User overridable callback/setting.
* <p/>
* Defines the default timeout for an initial receive invocation.
* When specified, the receive function should be able to handle a 'ReceiveTimeout' message.
*/
@volatile var receiveTimeout: Option[Long] = None
/**
* User overridable callback/setting.
@ -166,12 +165,12 @@ trait ActorRef extends TransactionManagement {
* The default is also that all actors that are created and spawned from within this actor
* is sharing the same dispatcher as its creator.
*/
private[akka] var _dispatcher: MessageDispatcher = Dispatchers.globalExecutorBasedEventDrivenDispatcher
@volatile private[akka] var _dispatcher: MessageDispatcher = Dispatchers.globalExecutorBasedEventDrivenDispatcher
/**
* Holds the hot swapped partial function.
*/
protected[akka] var hotswap: Option[PartialFunction[Any, Unit]] = None // FIXME: _hotswap should be a stack
@volatile protected[akka] var hotswap: Option[PartialFunction[Any, Unit]] = None // FIXME: _hotswap should be a stack
/**
* User overridable callback/setting.
@ -184,12 +183,12 @@ trait ActorRef extends TransactionManagement {
/**
* Configuration for TransactionFactory. User overridable.
*/
protected[akka] var _transactionConfig: TransactionConfig = DefaultGlobalTransactionConfig
@volatile protected[akka] var _transactionConfig: TransactionConfig = DefaultGlobalTransactionConfig
/**
* TransactionFactory to be used for atomic when isTransactor. Configuration is overridable.
*/
private[akka] var _transactionFactory: Option[TransactionFactory] = None
@volatile private[akka] var _transactionFactory: Option[TransactionFactory] = None
/**
* This lock ensures thread safety in the dispatching: only one message can
@ -204,6 +203,10 @@ trait ActorRef extends TransactionManagement {
protected[akka] def currentMessage_=(msg: Option[MessageInvocation]) = guard.withGuard { _currentMessage = msg }
protected[akka] def currentMessage = guard.withGuard { _currentMessage }
/** comparison only takes uuid into account
*/
def compareTo(other: ActorRef) = this.uuid.compareTo(other.uuid)
/**
* Returns the uuid for the actor.
@ -215,12 +218,10 @@ trait ActorRef extends TransactionManagement {
* Is defined if the message was sent from another Actor, else None.
*/
def sender: Option[ActorRef] = {
//Five lines of map-performance-avoidance, could be just: currentMessage map { _.sender }
// Five lines of map-performance-avoidance, could be just: currentMessage map { _.sender }
val msg = currentMessage
if(msg.isEmpty)
None
else
msg.get.sender
if(msg.isEmpty) None
else msg.get.sender
}
/**
@ -228,12 +229,10 @@ trait ActorRef extends TransactionManagement {
* Is defined if the message was sent with sent with '!!' or '!!!', else None.
*/
def senderFuture: Option[CompletableFuture[Any]] = {
//Five lines of map-performance-avoidance, could be just: currentMessage map { _.senderFuture }
// Five lines of map-performance-avoidance, could be just: currentMessage map { _.senderFuture }
val msg = currentMessage
if(msg.isEmpty)
None
else
msg.get.senderFuture
if(msg.isEmpty) None
else msg.get.senderFuture
}
/**
@ -296,15 +295,15 @@ trait ActorRef extends TransactionManagement {
def !!(message: Any, timeout: Long = this.timeout)(implicit sender: Option[ActorRef] = None): Option[Any] = {
if (isRunning) {
val future = postMessageToMailboxAndCreateFutureResultWithTimeout[Any](message, timeout, sender, None)
val isActiveObject = message.isInstanceOf[Invocation]
if (isActiveObject && message.asInstanceOf[Invocation].isVoid) {
val isTypedActor = message.isInstanceOf[Invocation]
if (isTypedActor && message.asInstanceOf[Invocation].isVoid) {
future.asInstanceOf[CompletableFuture[Option[_]]].completeWithResult(None)
}
try {
future.await
} catch {
case e: FutureTimeoutException =>
if (isActiveObject) throw e
if (isTypedActor) throw e
else None
}
if (future.exception.isDefined) throw future.exception.get._2
@ -352,7 +351,7 @@ trait ActorRef extends TransactionManagement {
"\n\tNo sender in scope, can't reply. " +
"\n\tYou have probably: " +
"\n\t\t1. Sent a message to an Actor from an instance that is NOT an Actor." +
"\n\t\t2. Invoked a method on an Active Object from an instance NOT an Active Object." +
"\n\t\t2. Invoked a method on an TypedActor from an instance NOT an TypedActor." +
"\n\tElse you might want to use 'reply_?' which returns Boolean(true) if succes and Boolean(false) if no sender in scope")
/**
@ -421,13 +420,13 @@ trait ActorRef extends TransactionManagement {
* Returns the home address and port for this actor.
*/
def homeAddress: InetSocketAddress = _homeAddress
/**
* Set the home address and port for this actor.
*/
def homeAddress_=(hostnameAndPort: Tuple2[String, Int]): Unit =
homeAddress_=(new InetSocketAddress(hostnameAndPort._1, hostnameAndPort._2))
/**
* Set the home address and port for this actor.
*/
@ -442,7 +441,7 @@ trait ActorRef extends TransactionManagement {
/**
* Starts up the actor and its message queue.
*/
def start: ActorRef
def start(): ActorRef
/**
* Shuts down the actor its dispatcher and message queue.
@ -462,64 +461,48 @@ trait ActorRef extends TransactionManagement {
* If the 'trapExit' member field has been set to at contain at least one exception class then it will
* 'trap' these exceptions and automatically restart the linked actors according to the restart strategy
* defined by the 'faultHandler'.
* <p/>
* To be invoked from within the actor itself.
*/
def link(actorRef: ActorRef): Unit
/**
* Unlink the actor.
* <p/>
* To be invoked from within the actor itself.
*/
def unlink(actorRef: ActorRef): Unit
/**
* Atomically start and link an actor.
* <p/>
* To be invoked from within the actor itself.
*/
def startLink(actorRef: ActorRef): Unit
/**
* Atomically start, link and make an actor remote.
* <p/>
* To be invoked from within the actor itself.
*/
def startLinkRemote(actorRef: ActorRef, hostname: String, port: Int): Unit
/**
* Atomically create (from actor class) and start an actor.
* <p/>
* To be invoked from within the actor itself.
*/
def spawn[T <: Actor : Manifest]: ActorRef
/**
* Atomically create (from actor class), start and make an actor remote.
* <p/>
* To be invoked from within the actor itself.
*/
def spawnRemote[T <: Actor: Manifest](hostname: String, port: Int): ActorRef
/**
* Atomically create (from actor class), start and link an actor.
* <p/>
* To be invoked from within the actor itself.
*/
def spawnLink[T <: Actor: Manifest]: ActorRef
/**
* Atomically create (from actor class), start, link and make an actor remote.
* <p/>
* To be invoked from within the actor itself.
*/
def spawnLinkRemote[T <: Actor : Manifest](hostname: String, port: Int): ActorRef
/**
* Returns the mailbox size.
*/
def mailboxSize: Int
def mailboxSize = dispatcher.mailboxSize(this)
/**
* Returns the supervisor, if there is one.
@ -547,13 +530,14 @@ trait ActorRef extends TransactionManagement {
protected[akka] def supervisor_=(sup: Option[ActorRef]): Unit
protected[akka] def mailbox: Deque[MessageInvocation]
protected[akka] def restart(reason: Throwable): Unit
protected[akka] def mailbox: AnyRef
protected[akka] def mailbox_=(value: AnyRef): AnyRef
protected[akka] def handleTrapExit(dead: ActorRef, reason: Throwable): Unit
protected[akka] def restartLinkedActors(reason: Throwable): Unit
protected[akka] def restart(reason: Throwable, maxNrOfRetries: Int, withinTimeRange: Int): Unit
protected[akka] def restartLinkedActors(reason: Throwable, maxNrOfRetries: Int, withinTimeRange: Int): Unit
protected[akka] def registerSupervisorAsRemoteActor: Option[String]
@ -571,23 +555,19 @@ trait ActorRef extends TransactionManagement {
override def toString = "Actor[" + id + ":" + uuid + "]"
protected[akka] def cancelReceiveTimeout = {
_timeoutActor.foreach {
x =>
if (x.isRunning) Scheduler.unschedule(x)
_timeoutActor = None
log.debug("Timeout canceled for %s", this)
}
}
protected [akka] def checkReceiveTimeout = {
protected[akka] def checkReceiveTimeout = {
cancelReceiveTimeout
receiveTimeout.foreach { timeout =>
receiveTimeout.foreach { time =>
log.debug("Scheduling timeout for %s", this)
_timeoutActor = Some(Scheduler.scheduleOnce(this, ReceiveTimeout, timeout, TimeUnit.MILLISECONDS))
_timeoutActor = Some(Scheduler.scheduleOnce(this, ReceiveTimeout, time, TimeUnit.MILLISECONDS))
}
}
protected[akka] def cancelReceiveTimeout = _timeoutActor.foreach { timeoutActor =>
if (timeoutActor.isRunning) Scheduler.unschedule(timeoutActor)
_timeoutActor = None
log.debug("Timeout canceled for %s", this)
}
}
/**
@ -595,12 +575,28 @@ trait ActorRef extends TransactionManagement {
*
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
sealed class LocalActorRef private[akka](
class LocalActorRef private[akka](
private[this] var actorFactory: Either[Option[Class[_ <: Actor]], Option[() => Actor]] = Left(None))
extends ActorRef {
private var isDeserialized = false
private var loader: Option[ClassLoader] = None
@volatile private[akka] var _remoteAddress: Option[InetSocketAddress] = None // only mutable to maintain identity across nodes
@volatile private[akka] var _linkedActors: Option[ConcurrentHashMap[String, ActorRef]] = None
@volatile private[akka] var _supervisor: Option[ActorRef] = None
@volatile private var isInInitialization = false
@volatile private var runActorInitialization = false
@volatile private var isDeserialized = false
@volatile private var loader: Option[ClassLoader] = None
@volatile private var maxNrOfRetriesCount: Int = 0
@volatile private var restartsWithinTimeRangeTimestamp: Long = 0L
@volatile private var _mailbox: AnyRef = _
protected[this] val actorInstance = guard.withGuard { new AtomicReference[Actor](newActor) }
// Needed to be able to null out the 'val self: ActorRef' member variables to make the Actor
// instance elegible for garbage collection
private val actorSelfFields = findActorSelfField(actor.getClass)
if (runActorInitialization && !isDeserialized) initializeActorInstance
private[akka] def this(clazz: Class[_ <: Actor]) = this(Left(Some(clazz)))
private[akka] def this(factory: () => Actor) = this(Right(Some(factory)))
@ -614,6 +610,7 @@ sealed class LocalActorRef private[akka](
__port: Int,
__isTransactor: Boolean,
__timeout: Long,
__receiveTimeout: Option[Long],
__lifeCycle: Option[LifeCycle],
__supervisor: Option[ActorRef],
__hotswap: Option[PartialFunction[Any, Unit]],
@ -635,6 +632,7 @@ sealed class LocalActorRef private[akka](
homeAddress = (__hostname, __port)
isTransactor = __isTransactor
timeout = __timeout
receiveTimeout = __receiveTimeout
lifeCycle = __lifeCycle
_supervisor = __supervisor
hotswap = __hotswap
@ -643,30 +641,11 @@ sealed class LocalActorRef private[akka](
actorSelfFields._3.set(actor, Some(this))
start
__messages.foreach(message => this ! MessageSerializer.deserialize(message.getMessage))
checkReceiveTimeout
ActorRegistry.register(this)
}
// Only mutable for RemoteServer in order to maintain identity across nodes
@volatile private[akka] var _remoteAddress: Option[InetSocketAddress] = None
@volatile private[akka] var _linkedActors: Option[ConcurrentHashMap[String, ActorRef]] = None
@volatile private[akka] var _supervisor: Option[ActorRef] = None
protected[akka] val _mailbox: Deque[MessageInvocation] = new ConcurrentLinkedDeque[MessageInvocation]
protected[this] val actorInstance = guard.withGuard { new AtomicReference[Actor](newActor) }
@volatile private var isInInitialization = false
@volatile private var runActorInitialization = false
// Needed to be able to null out the 'val self: ActorRef' member variables to make the Actor
// instance elegible for garbage collection
private val actorSelfFields = findActorSelfField(actor.getClass)
if (runActorInitialization && !isDeserialized) initializeActorInstance
/**
* Returns the mailbox.
*/
def mailbox: Deque[MessageInvocation] = _mailbox
// ========= PUBLIC FUNCTIONS =========
/**
* Returns the class for the Actor instance that is managed by the ActorRef.
@ -681,7 +660,7 @@ sealed class LocalActorRef private[akka](
/**
* Sets the dispatcher for this actor. Needs to be invoked before the actor is started.
*/
def dispatcher_=(md: MessageDispatcher): Unit = guard.withGuard {
def dispatcher_=(md: MessageDispatcher): Unit = {
if (!isRunning || isBeingRestarted) _dispatcher = md
else throw new ActorInitializationException(
"Can not swap dispatcher for " + toString + " after it has been started")
@ -690,7 +669,7 @@ sealed class LocalActorRef private[akka](
/**
* Get the dispatcher for this actor.
*/
def dispatcher: MessageDispatcher = guard.withGuard { _dispatcher }
def dispatcher: MessageDispatcher = _dispatcher
/**
* Invoking 'makeRemote' means that an actor will be moved to and invoked on a remote host.
@ -734,19 +713,19 @@ sealed class LocalActorRef private[akka](
/**
* Get the transaction configuration for this actor.
*/
def transactionConfig: TransactionConfig = guard.withGuard { _transactionConfig }
def transactionConfig: TransactionConfig = _transactionConfig
/**
* Set the contact address for this actor. This is used for replying to messages
* sent asynchronously when no reply channel exists.
*/
def homeAddress_=(address: InetSocketAddress): Unit = guard.withGuard { _homeAddress = address }
def homeAddress_=(address: InetSocketAddress): Unit = _homeAddress = address
/**
* Returns the remote address for the actor, if any, else None.
*/
def remoteAddress: Option[InetSocketAddress] = guard.withGuard { _remoteAddress }
protected[akka] def remoteAddress_=(addr: Option[InetSocketAddress]): Unit = guard.withGuard { _remoteAddress = addr }
def remoteAddress: Option[InetSocketAddress] = _remoteAddress
protected[akka] def remoteAddress_=(addr: Option[InetSocketAddress]): Unit = _remoteAddress = addr
/**
* Starts up the actor and its message queue.
@ -783,7 +762,7 @@ sealed class LocalActorRef private[akka](
address.getHostName, address.getPort, uuid))
RemoteNode.unregister(this)
nullOutActorRefReferencesFor(actorInstance.get)
} else if (isBeingRestarted) throw new ActorKilledException("Actor [" + toString + "] is being restarted.")
} //else if (isBeingRestarted) throw new ActorKilledException("Actor [" + toString + "] is being restarted.")
}
/**
@ -898,19 +877,16 @@ sealed class LocalActorRef private[akka](
}
/**
* Returns the mailbox size.
* Returns the mailbox.
*/
def mailboxSize: Int = _mailbox.size
def mailbox: AnyRef = _mailbox
/**
* Returns a copy of all the messages, put into a List[MessageInvocation].
*/
def messagesInMailbox: List[MessageInvocation] = _mailbox.toArray.toList.asInstanceOf[List[MessageInvocation]]
protected[akka] def mailbox_=(value: AnyRef):AnyRef = { _mailbox = value; value }
/**
* Shuts down and removes all linked actors.
*/
def shutdownLinkedActors(): Unit = guard.withGuard {
def shutdownLinkedActors(): Unit = {
linkedActorsAsList.foreach(_.stop)
linkedActors.clear
}
@ -918,9 +894,173 @@ sealed class LocalActorRef private[akka](
/**
* Returns the supervisor, if there is one.
*/
def supervisor: Option[ActorRef] = guard.withGuard { _supervisor }
def supervisor: Option[ActorRef] = _supervisor
protected[akka] def supervisor_=(sup: Option[ActorRef]): Unit = guard.withGuard { _supervisor = sup }
// ========= AKKA PROTECTED FUNCTIONS =========
protected[akka] def supervisor_=(sup: Option[ActorRef]): Unit = _supervisor = sup
protected[akka] def postMessageToMailbox(message: Any, senderOption: Option[ActorRef]): Unit = {
joinTransaction(message)
if (remoteAddress.isDefined) {
RemoteClient.clientFor(remoteAddress.get).send[Any](
createRemoteRequestProtocolBuilder(this, message, true, senderOption).build, None)
} else {
val invocation = new MessageInvocation(this, message, senderOption, None, transactionSet.get)
invocation.send
}
}
protected[akka] def postMessageToMailboxAndCreateFutureResultWithTimeout[T](
message: Any,
timeout: Long,
senderOption: Option[ActorRef],
senderFuture: Option[CompletableFuture[T]]): CompletableFuture[T] = {
joinTransaction(message)
if (remoteAddress.isDefined) {
val future = RemoteClient.clientFor(remoteAddress.get).send(
createRemoteRequestProtocolBuilder(this, message, false, senderOption).build, senderFuture)
if (future.isDefined) future.get
else throw new IllegalActorStateException("Expected a future from remote call to actor " + toString)
} else {
val future = if (senderFuture.isDefined) senderFuture.get
else new DefaultCompletableFuture[T](timeout)
val invocation = new MessageInvocation(
this, message, senderOption, Some(future.asInstanceOf[CompletableFuture[Any]]), transactionSet.get)
invocation.send
future
}
}
/**
* Callback for the dispatcher. This is the ingle entry point to the user Actor implementation.
*/
protected[akka] def invoke(messageHandle: MessageInvocation): Unit = guard.withGuard {
if (isShutdown)
Actor.log.warning("Actor [%s] is shut down,\n\tignoring message [%s]", toString, messageHandle)
else {
currentMessage = Option(messageHandle)
try {
dispatch(messageHandle)
} catch {
case e =>
Actor.log.error(e, "Could not invoke actor [%s]", this)
throw e
} finally {
currentMessage = None //TODO: Don't reset this, we might want to resend the message
}
}
}
protected[akka] def handleTrapExit(dead: ActorRef, reason: Throwable): Unit = {
if (trapExit.exists(_.isAssignableFrom(reason.getClass))) {
faultHandler match {
case Some(AllForOneStrategy(maxNrOfRetries, withinTimeRange)) =>
restartLinkedActors(reason, maxNrOfRetries, withinTimeRange)
case Some(OneForOneStrategy(maxNrOfRetries, withinTimeRange)) =>
dead.restart(reason, maxNrOfRetries, withinTimeRange)
case None => throw new IllegalActorStateException(
"No 'faultHandler' defined for an actor with the 'trapExit' member field defined " +
"\n\tto non-empty list of exception classes - can't proceed " + toString)
}
} else {
notifySupervisorWithMessage(Exit(this, reason)) // if 'trapExit' is not defined then pass the Exit on
}
}
protected[akka] def restart(reason: Throwable, maxNrOfRetries: Int, withinTimeRange: Int): Unit = {
if (maxNrOfRetriesCount == 0) restartsWithinTimeRangeTimestamp = System.currentTimeMillis // first time around
maxNrOfRetriesCount += 1
val tooManyRestarts = maxNrOfRetriesCount > maxNrOfRetries
val restartingHasExpired = (System.currentTimeMillis - restartsWithinTimeRangeTimestamp) > withinTimeRange
if (tooManyRestarts || restartingHasExpired) {
val notification = MaximumNumberOfRestartsWithinTimeRangeReached(this, maxNrOfRetries, withinTimeRange, reason)
Actor.log.warning(
"Maximum number of restarts [%s] within time range [%s] reached." +
"\n\tWill *not* restart actor [%s] anymore." +
"\n\tLast exception causing restart was" +
"\n\t[%s].",
maxNrOfRetries, withinTimeRange, this, reason)
_supervisor.foreach { sup =>
// can supervisor handle the notification?
if (sup.isDefinedAt(notification)) notifySupervisorWithMessage(notification)
else Actor.log.warning(
"No message handler defined for system message [MaximumNumberOfRestartsWithinTimeRangeReached]" +
"\n\tCan't send the message to the supervisor [%s].", sup)
}
stop
} else {
_isBeingRestarted = true
val failedActor = actorInstance.get
guard.withGuard {
lifeCycle match {
case Some(LifeCycle(Temporary)) => shutDownTemporaryActor(this)
case _ =>
// either permanent or none where default is permanent
Actor.log.info("Restarting actor [%s] configured as PERMANENT.", id)
Actor.log.debug("Restarting linked actors for actor [%s].", id)
restartLinkedActors(reason, maxNrOfRetries, withinTimeRange)
Actor.log.debug("Invoking 'preRestart' for failed actor instance [%s].", id)
if (isTypedActorDispatcher(failedActor)) restartTypedActorDispatcher(failedActor, reason)
else restartActor(failedActor, reason)
_isBeingRestarted = false
}
}
}
}
protected[akka] def restartLinkedActors(reason: Throwable, maxNrOfRetries: Int, withinTimeRange: Int) = {
linkedActorsAsList.foreach { actorRef =>
actorRef.lifeCycle match {
// either permanent or none where default is permanent
case Some(LifeCycle(Temporary)) => shutDownTemporaryActor(actorRef)
case _ => actorRef.restart(reason, maxNrOfRetries, withinTimeRange)
}
}
}
protected[akka] def registerSupervisorAsRemoteActor: Option[String] = guard.withGuard {
if (_supervisor.isDefined) {
RemoteClient.clientFor(remoteAddress.get).registerSupervisorForActor(this)
Some(_supervisor.get.uuid)
} else None
}
protected[akka] def linkedActors: JMap[String, ActorRef] = guard.withGuard {
if (_linkedActors.isEmpty) {
val actors = new ConcurrentHashMap[String, ActorRef]
_linkedActors = Some(actors)
actors
} else _linkedActors.get
}
protected[akka] def linkedActorsAsList: List[ActorRef] =
linkedActors.values.toArray.toList.asInstanceOf[List[ActorRef]]
// ========= PRIVATE FUNCTIONS =========
private def isTypedActorDispatcher(a: Actor): Boolean = a.isInstanceOf[Dispatcher]
private def restartTypedActorDispatcher(failedActor: Actor, reason: Throwable) = {
failedActor.preRestart(reason)
failedActor.postRestart(reason)
}
private def restartActor(failedActor: Actor, reason: Throwable) = {
failedActor.preRestart(reason)
nullOutActorRefReferencesFor(failedActor)
val freshActor = newActor
freshActor.init
freshActor.initTransactionalState
actorInstance.set(freshActor)
Actor.log.debug("Invoking 'postRestart' for new actor instance [%s].", id)
freshActor.postRestart(reason)
}
private def spawnButDoNotStart[T <: Actor: Manifest]: ActorRef = guard.withGuard {
val actorRef = Actor.actorOf(manifest[T].erasure.asInstanceOf[Class[T]].newInstance)
@ -954,74 +1094,23 @@ sealed class LocalActorRef private[akka](
actor
}
protected[akka] def postMessageToMailbox(message: Any, senderOption: Option[ActorRef]): Unit = {
joinTransaction(message)
if (remoteAddress.isDefined) {
RemoteClient.clientFor(remoteAddress.get).send[Any](
createRemoteRequestProtocolBuilder(this, message, true, senderOption).build, None)
} else {
val invocation = new MessageInvocation(this, message, senderOption, None, transactionSet.get)
if (dispatcher.usesActorMailbox) {
_mailbox.add(invocation)
invocation.send
} else invocation.send
}
}
protected[akka] def postMessageToMailboxAndCreateFutureResultWithTimeout[T](
message: Any,
timeout: Long,
senderOption: Option[ActorRef],
senderFuture: Option[CompletableFuture[T]]): CompletableFuture[T] = {
joinTransaction(message)
if (remoteAddress.isDefined) {
val future = RemoteClient.clientFor(remoteAddress.get).send(
createRemoteRequestProtocolBuilder(this, message, false, senderOption).build, senderFuture)
if (future.isDefined) future.get
else throw new IllegalActorStateException("Expected a future from remote call to actor " + toString)
} else {
val future = if (senderFuture.isDefined) senderFuture.get
else new DefaultCompletableFuture[T](timeout)
val invocation = new MessageInvocation(
this, message, senderOption, Some(future.asInstanceOf[CompletableFuture[Any]]), transactionSet.get)
if (dispatcher.usesActorMailbox) _mailbox.add(invocation)
invocation.send
future
}
}
private def joinTransaction(message: Any) = if (isTransactionSetInScope) {
import org.multiverse.api.ThreadLocalTransaction
val txSet = getTransactionSetInScope
Actor.log.trace("Joining transaction set [%s];\n\tactor %s\n\twith message [%s]", txSet, toString, message) // FIXME test to run bench without this trace call
val oldTxSet = getTransactionSetInScope
val currentTxSet = if (oldTxSet.isAborted || oldTxSet.isCommitted) {
clearTransactionSet
createNewTransactionSet
} else oldTxSet
Actor.log.ifTrace("Joining transaction set [" + currentTxSet +
"];\n\tactor " + toString +
"\n\twith message [" + message + "]")
val mtx = ThreadLocalTransaction.getThreadLocalTransaction
if ((mtx eq null) || mtx.getStatus.isDead) txSet.incParties
else txSet.incParties(mtx, 1)
}
/**
* Callback for the dispatcher. This is the ingle entry point to the user Actor implementation.
*/
protected[akka] def invoke(messageHandle: MessageInvocation): Unit = actor.synchronized {
if (isShutdown) {
Actor.log.warning("Actor [%s] is shut down, ignoring message [%s]", toString, messageHandle)
return
}
currentMessage = Option(messageHandle)
try {
dispatch(messageHandle)
} catch {
case e =>
Actor.log.error(e, "Could not invoke actor [%s]", this)
throw e
} finally {
currentMessage = None //TODO: Don't reset this, we might want to resend the message
}
if ((mtx eq null) || mtx.getStatus.isDead) currentTxSet.incParties
else currentTxSet.incParties(mtx, 1)
}
private def dispatch[T](messageHandle: MessageInvocation) = {
Actor.log.ifTrace("Invoking actor with message:\n" + messageHandle)
val message = messageHandle.message //serializeMessage(messageHandle.message)
var topLevelTransaction = false
val txSet: Option[CountDownCommitBarrier] =
@ -1029,9 +1118,8 @@ sealed class LocalActorRef private[akka](
else {
topLevelTransaction = true // FIXME create a new internal atomic block that can wait for X seconds if top level tx
if (isTransactor) {
Actor.log.trace(
"Creating a new transaction set (top-level transaction)\n\tfor actor %s\n\twith message %s",
toString, messageHandle)
Actor.log.ifTrace("Creating a new transaction set (top-level transaction)\n\tfor actor " + toString +
"\n\twith message " + messageHandle)
Some(createNewTransactionSet)
} else None
}
@ -1050,93 +1138,18 @@ sealed class LocalActorRef private[akka](
setTransactionSet(txSet) // restore transaction set to allow atomic block to do commit
}
} catch {
case e =>
_isBeingRestarted = true
// abort transaction set
if (isTransactionSetInScope) {
val txSet = getTransactionSetInScope
Actor.log.debug("Aborting transaction set [%s]", txSet)
txSet.abort
}
Actor.log.error(e, "Exception when invoking \n\tactor [%s] \n\twith message [%s]", this, message)
senderFuture.foreach(_.completeWithException(this, e))
clearTransaction
if (topLevelTransaction) clearTransactionSet
// 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)
case e: DeadTransactionException =>
handleExceptionInDispatch(
new TransactionSetAbortedException("Transaction set has been aborted by another participant"),
message, topLevelTransaction)
case e: InterruptedException => {} // received message while actor is shutting down, ignore
case e => handleExceptionInDispatch(e, message, topLevelTransaction)
} finally {
clearTransaction
if (topLevelTransaction) clearTransactionSet
}
}
protected[akka] def handleTrapExit(dead: ActorRef, reason: Throwable): Unit = {
if (trapExit.exists(_.isAssignableFrom(reason.getClass))) {
faultHandler match {
// FIXME: implement support for maxNrOfRetries and withinTimeRange in RestartStrategy
case Some(AllForOneStrategy(maxNrOfRetries, withinTimeRange)) =>
restartLinkedActors(reason)
case Some(OneForOneStrategy(maxNrOfRetries, withinTimeRange)) =>
dead.restart(reason)
case None =>
throw new IllegalActorStateException(
"No 'faultHandler' defined for an actor with the 'trapExit' member field defined " +
"\n\tto non-empty list of exception classes - can't proceed " + toString)
}
} else {
if (lifeCycle.isEmpty) lifeCycle = Some(LifeCycle(Permanent)) // when passing on make sure we have a lifecycle
_supervisor.foreach(_ ! Exit(this, reason)) // if 'trapExit' is not defined then pass the Exit on
}
}
protected[akka] def restart(reason: Throwable): Unit = {
val failedActor = actorInstance.get
failedActor.synchronized {
lifeCycle.get match {
case LifeCycle(scope, _, _) => {
scope match {
case Permanent =>
Actor.log.info("Restarting actor [%s] configured as PERMANENT.", id)
restartLinkedActors(reason)
Actor.log.debug("Restarting linked actors for actor [%s].", id)
Actor.log.debug("Invoking 'preRestart' for failed actor instance [%s].", id)
failedActor.preRestart(reason)
nullOutActorRefReferencesFor(failedActor)
val freshActor = newActor
freshActor.synchronized {
freshActor.init
freshActor.initTransactionalState
actorInstance.set(freshActor)
Actor.log.debug("Invoking 'postRestart' for new actor instance [%s].", id)
freshActor.postRestart(reason)
}
_isBeingRestarted = false
case Temporary => shutDownTemporaryActor(this)
}
}
}
}
}
protected[akka] def restartLinkedActors(reason: Throwable) = guard.withGuard {
linkedActorsAsList.foreach { actorRef =>
if (actorRef.lifeCycle.isEmpty) actorRef.lifeCycle = Some(LifeCycle(Permanent))
actorRef.lifeCycle.get match {
case LifeCycle(scope, _, _) => {
scope match {
case Permanent => actorRef.restart(reason)
case Temporary => shutDownTemporaryActor(actorRef)
}
}
}
}
}
private def shutDownTemporaryActor(temporaryActor: ActorRef) = {
Actor.log.info("Actor [%s] configured as TEMPORARY and will not be restarted.", temporaryActor.id)
temporaryActor.stop
@ -1147,28 +1160,41 @@ sealed class LocalActorRef private[akka](
"All linked actors have died permanently (they were all configured as TEMPORARY)" +
"\n\tshutting down and unlinking supervisor actor as well [%s].",
temporaryActor.id)
_supervisor.foreach(_ ! UnlinkAndStop(this))
notifySupervisorWithMessage(UnlinkAndStop(this))
}
}
protected[akka] def registerSupervisorAsRemoteActor: Option[String] = guard.withGuard {
if (_supervisor.isDefined) {
RemoteClient.clientFor(remoteAddress.get).registerSupervisorForActor(this)
Some(_supervisor.get.uuid)
} else None
private def handleExceptionInDispatch(reason: Throwable, message: Any, topLevelTransaction: Boolean) = {
Actor.log.error(reason, "Exception when invoking \n\tactor [%s] \n\twith message [%s]", this, message)
_isBeingRestarted = true
// abort transaction set
if (isTransactionSetInScope) {
val txSet = getTransactionSetInScope
if (!txSet.isCommitted) {
Actor.log.debug("Aborting transaction set [%s]", txSet)
txSet.abort
}
}
senderFuture.foreach(_.completeWithException(this, reason))
clearTransaction
if (topLevelTransaction) clearTransactionSet
notifySupervisorWithMessage(Exit(this, reason))
}
protected[akka] def linkedActors: JMap[String, ActorRef] = guard.withGuard {
if (_linkedActors.isEmpty) {
val actors = new ConcurrentHashMap[String, ActorRef]
_linkedActors = Some(actors)
actors
} else _linkedActors.get
private def notifySupervisorWithMessage(notification: LifeCycleMessage) = {
// FIXME to fix supervisor restart of remote actor for oneway calls, inject a supervisor proxy that can send notification back to client
_supervisor.foreach { sup =>
if (sup.isShutdown) { // if supervisor is shut down, game over for all linked actors
// shutdownLinkedActors
// stop
} else sup ! notification // else notify supervisor
}
}
protected[akka] def linkedActorsAsList: List[ActorRef] =
linkedActors.values.toArray.toList.asInstanceOf[List[ActorRef]]
private def nullOutActorRefReferencesFor(actor: Actor) = {
actorSelfFields._1.set(actor, null)
actorSelfFields._2.set(actor, null)
@ -1188,7 +1214,8 @@ sealed class LocalActorRef private[akka](
case e: NoSuchFieldException =>
val parent = clazz.getSuperclass
if (parent != null) findActorSelfField(parent)
else throw new IllegalActorStateException(toString + " is not an Actor since it have not mixed in the 'Actor' trait")
else throw new IllegalActorStateException(
toString + " is not an Actor since it have not mixed in the 'Actor' trait")
}
}
@ -1293,13 +1320,13 @@ private[akka] case class RemoteActorRef private[akka] (
def spawnRemote[T <: Actor: Manifest](hostname: String, port: Int): ActorRef = unsupported
def spawnLink[T <: Actor: Manifest]: ActorRef = unsupported
def spawnLinkRemote[T <: Actor : Manifest](hostname: String, port: Int): ActorRef = unsupported
def mailboxSize: Int = unsupported
def supervisor: Option[ActorRef] = unsupported
def shutdownLinkedActors: Unit = unsupported
protected[akka] def mailbox: Deque[MessageInvocation] = unsupported
protected[akka] def restart(reason: Throwable): Unit = unsupported
protected[akka] def mailbox: AnyRef = unsupported
protected[akka] def mailbox_=(value: AnyRef):AnyRef = unsupported
protected[akka] def handleTrapExit(dead: ActorRef, reason: Throwable): Unit = unsupported
protected[akka] def restartLinkedActors(reason: Throwable): Unit = unsupported
protected[akka] def restart(reason: Throwable, maxNrOfRetries: Int, withinTimeRange: Int): Unit = unsupported
protected[akka] def restartLinkedActors(reason: Throwable, maxNrOfRetries: Int, withinTimeRange: Int): Unit = unsupported
protected[akka] def linkedActors: JMap[String, ActorRef] = unsupported
protected[akka] def linkedActorsAsList: List[ActorRef] = unsupported
protected[akka] def invoke(messageHandle: MessageInvocation): Unit = unsupported

View file

@ -29,11 +29,6 @@ case class ActorUnregistered(actor: ActorRef) extends ActorRegistryEvent
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
object ActorRegistry extends ListenerManagement {
private val refComparator = new java.util.Comparator[ActorRef]{
def compare(a: ActorRef,b: ActorRef) = a.uuid.compareTo(b.uuid)
}
private val actorsByUUID = new ConcurrentHashMap[String, ActorRef]
private val actorsById = new ConcurrentHashMap[String, JSet[ActorRef]]
private val actorsByClassName = new ConcurrentHashMap[String, JSet[ActorRef]]
@ -122,16 +117,16 @@ object ActorRegistry extends ListenerManagement {
if (id eq null) throw new IllegalActorStateException("Actor.id is null " + actor)
if (actorsById.containsKey(id)) actorsById.get(id).add(actor)
else {
val set = new ConcurrentSkipListSet[ActorRef](refComparator)
val set = new ConcurrentSkipListSet[ActorRef]
set.add(actor)
actorsById.put(id, set)
}
// Class name
val className = actor.actor.getClass.getName
val className = actor.actorClassName
if (actorsByClassName.containsKey(className)) actorsByClassName.get(className).add(actor)
else {
val set = new ConcurrentSkipListSet[ActorRef](refComparator)
val set = new ConcurrentSkipListSet[ActorRef]
set.add(actor)
actorsByClassName.put(className, set)
}
@ -149,7 +144,7 @@ object ActorRegistry extends ListenerManagement {
val id = actor.id
if (actorsById.containsKey(id)) actorsById.get(id).remove(actor)
val className = actor.getClass.getName
val className = actor.actorClassName
if (actorsByClassName.containsKey(className)) actorsByClassName.get(className).remove(actor)
// notify listeners
@ -159,7 +154,7 @@ object ActorRegistry extends ListenerManagement {
/**
* Shuts down and unregisters all actors in the system.
*/
def shutdownAll = {
def shutdownAll() {
log.info("Shutting down all actors in the system...")
foreach(_.stop)
actorsByUUID.clear

View file

@ -20,7 +20,7 @@ import java.util.concurrent._
import se.scalablesolutions.akka.util.Logging
object Scheduler {
object Scheduler extends Logging {
import Actor._
case object UnSchedule
@ -28,8 +28,12 @@ object Scheduler {
private var service = Executors.newSingleThreadScheduledExecutor(SchedulerThreadFactory)
private val schedulers = new ConcurrentHashMap[ActorRef, ActorRef]
log.info("Starting up Scheduler")
def schedule(receiver: ActorRef, message: AnyRef, initialDelay: Long, delay: Long, timeUnit: TimeUnit): ActorRef = {
log.trace(
"Schedule scheduled event\n\tevent = [%s]\n\treceiver = [%s]\n\tinitialDelay = [%s]\n\tdelay = [%s]\n\ttimeUnit = [%s]",
message, receiver, initialDelay, delay, timeUnit)
try {
val future = service.scheduleAtFixedRate(
new Runnable { def run = receiver ! message },
@ -44,6 +48,9 @@ object Scheduler {
}
def scheduleOnce(receiver: ActorRef, message: AnyRef, delay: Long, timeUnit: TimeUnit): ActorRef = {
log.trace(
"Schedule one-time event\n\tevent = [%s]\n\treceiver = [%s]\n\tdelay = [%s]\n\ttimeUnit = [%s]",
message, receiver, delay, timeUnit)
try {
val future = service.schedule(
new Runnable { def run = receiver ! message }, delay, timeUnit).asInstanceOf[ScheduledFuture[AnyRef]]
@ -65,6 +72,7 @@ object Scheduler {
}
def shutdown = {
log.info("Shutting down Scheduler")
import scala.collection.JavaConversions._
schedulers.values.foreach(_ ! UnSchedule)
schedulers.clear
@ -72,14 +80,16 @@ object Scheduler {
}
def restart = {
log.info("Restarting Scheduler")
shutdown
service = Executors.newSingleThreadScheduledExecutor(SchedulerThreadFactory)
}
}
private class ScheduleActor(future: ScheduledFuture[AnyRef]) extends Actor with Logging {
private class ScheduleActor(future: ScheduledFuture[AnyRef]) extends Actor {
def receive = {
case Scheduler.UnSchedule =>
Scheduler.log.trace("Unschedule event handled by scheduleActor\n\tactorRef = [%s]", self.toString)
future.cancel(true)
self.stop
}
@ -91,7 +101,7 @@ private object SchedulerThreadFactory extends ThreadFactory {
def newThread(r: Runnable): Thread = {
val thread = threadFactory.newThread(r)
thread.setName("Scheduler-" + count)
thread.setName("akka:scheduler-" + count)
thread.setDaemon(true)
thread
}

View file

@ -77,42 +77,38 @@ object ActorSerialization {
toSerializedActorRefProtocol(a, format).toByteArray
}
private def toSerializedActorRefProtocol[T <: Actor](a: ActorRef, format: Format[T]): SerializedActorRefProtocol = {
private def toSerializedActorRefProtocol[T <: Actor](actorRef: ActorRef, format: Format[T]): SerializedActorRefProtocol = {
val lifeCycleProtocol: Option[LifeCycleProtocol] = {
def setScope(builder: LifeCycleProtocol.Builder, scope: Scope) = scope match {
case Permanent => builder.setLifeCycle(LifeCycleType.PERMANENT)
case Temporary => builder.setLifeCycle(LifeCycleType.TEMPORARY)
}
val builder = LifeCycleProtocol.newBuilder
a.lifeCycle match {
case Some(LifeCycle(scope, None, _)) =>
actorRef.lifeCycle match {
case Some(LifeCycle(scope)) =>
setScope(builder, scope)
Some(builder.build)
case Some(LifeCycle(scope, Some(callbacks), _)) =>
setScope(builder, scope)
builder.setPreRestart(callbacks.preRestart)
builder.setPostRestart(callbacks.postRestart)
Some(builder.build)
case None => None
}
}
val originalAddress = AddressProtocol.newBuilder
.setHostname(a.homeAddress.getHostName)
.setPort(a.homeAddress.getPort)
.setHostname(actorRef.homeAddress.getHostName)
.setPort(actorRef.homeAddress.getPort)
.build
val builder = SerializedActorRefProtocol.newBuilder
.setUuid(a.uuid)
.setId(a.id)
.setActorClassname(a.actorClass.getName)
.setUuid(actorRef.uuid)
.setId(actorRef.id)
.setActorClassname(actorRef.actorClass.getName)
.setOriginalAddress(originalAddress)
.setIsTransactor(a.isTransactor)
.setTimeout(a.timeout)
.setIsTransactor(actorRef.isTransactor)
.setTimeout(actorRef.timeout)
builder.setActorInstance(ByteString.copyFrom(format.toBinary(a.actor.asInstanceOf[T])))
actorRef.receiveTimeout.foreach(builder.setReceiveTimeout(_))
builder.setActorInstance(ByteString.copyFrom(format.toBinary(actorRef.actor.asInstanceOf[T])))
lifeCycleProtocol.foreach(builder.setLifeCycle(_))
a.supervisor.foreach(s => builder.setSupervisor(RemoteActorSerialization.toRemoteActorRefProtocol(s)))
actorRef.supervisor.foreach(s => builder.setSupervisor(RemoteActorSerialization.toRemoteActorRefProtocol(s)))
// FIXME: how to serialize the hotswap PartialFunction ??
//hotswap.foreach(builder.setHotswapStack(_))
builder.build
@ -121,7 +117,8 @@ object ActorSerialization {
private def fromBinaryToLocalActorRef[T <: Actor](bytes: Array[Byte], format: Format[T]): ActorRef =
fromProtobufToLocalActorRef(SerializedActorRefProtocol.newBuilder.mergeFrom(bytes).build, format, None)
private def fromProtobufToLocalActorRef[T <: Actor](protocol: SerializedActorRefProtocol, format: Format[T], loader: Option[ClassLoader]): ActorRef = {
private def fromProtobufToLocalActorRef[T <: Actor](
protocol: SerializedActorRefProtocol, format: Format[T], loader: Option[ClassLoader]): ActorRef = {
Actor.log.debug("Deserializing SerializedActorRefProtocol to LocalActorRef:\n" + protocol)
val serializer =
@ -132,12 +129,8 @@ object ActorSerialization {
val lifeCycle =
if (protocol.hasLifeCycle) {
val lifeCycleProtocol = protocol.getLifeCycle
val restartCallbacks =
if (lifeCycleProtocol.hasPreRestart || lifeCycleProtocol.hasPostRestart)
Some(RestartCallbacks(lifeCycleProtocol.getPreRestart, lifeCycleProtocol.getPostRestart))
else None
Some(if (lifeCycleProtocol.getLifeCycle == LifeCycleType.PERMANENT) LifeCycle(Permanent, restartCallbacks)
else if (lifeCycleProtocol.getLifeCycle == LifeCycleType.TEMPORARY) LifeCycle(Temporary, restartCallbacks)
Some(if (lifeCycleProtocol.getLifeCycle == LifeCycleType.PERMANENT) LifeCycle(Permanent)
else if (lifeCycleProtocol.getLifeCycle == LifeCycleType.TEMPORARY) LifeCycle(Temporary)
else throw new IllegalActorStateException("LifeCycle type is not valid: " + lifeCycleProtocol.getLifeCycle))
} else None
@ -161,6 +154,7 @@ object ActorSerialization {
protocol.getOriginalAddress.getPort,
if (protocol.hasIsTransactor) protocol.getIsTransactor else false,
if (protocol.hasTimeout) protocol.getTimeout else Actor.TIMEOUT,
if (protocol.hasReceiveTimeout) Some(protocol.getReceiveTimeout) else None,
lifeCycle,
supervisor,
hotswap,
@ -223,26 +217,30 @@ object RemoteActorSerialization {
.build
}
def createRemoteRequestProtocolBuilder(ar: ActorRef,
message: Any, isOneWay: Boolean, senderOption: Option[ActorRef]): RemoteRequestProtocol.Builder = {
import ar._
val protocol = RemoteRequestProtocol.newBuilder
.setId(RemoteRequestProtocolIdFactory.nextId)
.setMessage(MessageSerializer.serialize(message))
def createRemoteRequestProtocolBuilder(actorRef: ActorRef, message: Any, isOneWay: Boolean, senderOption: Option[ActorRef]):
RemoteRequestProtocol.Builder = {
import actorRef._
val actorInfo = ActorInfoProtocol.newBuilder
.setUuid(uuid)
.setTarget(actorClassName)
.setTimeout(timeout)
.setUuid(uuid)
.setIsActor(true)
.setActorType(ActorType.SCALA_ACTOR)
.build
val request = RemoteRequestProtocol.newBuilder
.setId(RemoteRequestProtocolIdFactory.nextId)
.setMessage(MessageSerializer.serialize(message))
.setActorInfo(actorInfo)
.setIsOneWay(isOneWay)
.setIsEscaped(false)
val id = registerSupervisorAsRemoteActor
if (id.isDefined) protocol.setSupervisorUuid(id.get)
if (id.isDefined) request.setSupervisorUuid(id.get)
senderOption.foreach { sender =>
RemoteServer.getOrCreateServer(sender.homeAddress).register(sender.uuid, sender)
protocol.setSender(toRemoteActorRefProtocol(sender))
request.setSender(toRemoteActorRefProtocol(sender))
}
protocol
request
}
}

View file

@ -161,8 +161,8 @@ sealed class Supervisor private[akka] (
_childActors.put(className, actorRef :: currentActors)
actorRef.lifeCycle = Some(lifeCycle)
supervisor.link(actorRef)
remoteAddress.foreach(address =>
RemoteServer.registerActor(new InetSocketAddress(address.hostname, address.port), actorRef.uuid, actorRef))
remoteAddress.foreach(address => RemoteServer.registerActor(
new InetSocketAddress(address.hostname, address.port), actorRef.uuid, actorRef))
case supervisorConfig @ SupervisorConfig(_, _) => // recursive supervisor configuration
val childSupervisor = Supervisor(supervisorConfig)
supervisor.link(childSupervisor.supervisor)
@ -180,14 +180,23 @@ final class SupervisorActor private[akka] (
handler: FaultHandlingStrategy,
trapExceptions: List[Class[_ <: Throwable]]) extends Actor {
import self._
trapExit = trapExceptions
faultHandler = Some(handler)
override def shutdown(): Unit = shutdownLinkedActors
def receive = {
// FIXME add a way to respond to MaximumNumberOfRestartsWithinTimeRangeReached in declaratively configured Supervisor
case MaximumNumberOfRestartsWithinTimeRangeReached(
victim, maxNrOfRetries, withinTimeRange, lastExceptionCausingRestart) =>
Actor.log.warning(
"Declaratively configured supervisor received a [MaximumNumberOfRestartsWithinTimeRangeReached] notification," +
"\n\tbut there is currently no way of handling it in a declaratively configured supervisor." +
"\n\tIf you want to be able to handle this error condition then you need to create the supervision tree programatically." +
"\n\tThis will be supported in the future.")
case unknown => throw new SupervisorException(
"SupervisorActor can not respond to messages. Unknown message [" + unknown + "]")
"SupervisorActor can not respond to messages.\n\tUnknown message [" + unknown + "]")
}
}

View file

@ -0,0 +1,804 @@
/**
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
*/
package se.scalablesolutions.akka.actor
import Actor._
import se.scalablesolutions.akka.config.FaultHandlingStrategy
import se.scalablesolutions.akka.remote.protocol.RemoteProtocol._
import se.scalablesolutions.akka.remote.{MessageSerializer, RemoteClient, RemoteRequestProtocolIdFactory}
import se.scalablesolutions.akka.dispatch.{MessageDispatcher, Future, CompletableFuture}
import se.scalablesolutions.akka.config.ScalaConfig._
import se.scalablesolutions.akka.serialization.Serializer
import se.scalablesolutions.akka.util._
import org.codehaus.aspectwerkz.joinpoint.{MethodRtti, JoinPoint}
import org.codehaus.aspectwerkz.proxy.Proxy
import org.codehaus.aspectwerkz.annotation.{Aspect, Around}
import java.net.InetSocketAddress
import java.lang.reflect.{InvocationTargetException, Method, Field}
import scala.reflect.BeanProperty
/**
* FIXME: document TypedActor
*
* Here is an example of usage (in Java):
* <pre>
* class PingImpl extends TypedActor implements Ping {
* public void hit(int count) {
* Pong pong = (Pong) getContext().getSender();
* pong.hit(count++);
* }
*
* @Override
* public void init() {
* ... // optional initialization on start
* }
*
* @Override
* public void shutdown() {
* ... // optional cleanup on stop
* }
*
* ... // more life-cycle callbacks if needed
* }
*
* // create the ping actor
* Ping ping = TypedActor.newInstance(Ping.class, PingImpl.class);
*
* ping.hit(1); // use the actor
* ping.hit(1);
*
* // stop the actor
* TypedActor.stop(ping);
* </pre>
*
* Here is an example of usage (in Scala):
* <pre>
* class PingImpl extends TypedActor with Ping {
* def hit(count: Int) = {
* val pong = context.sender.asInstanceOf[Pong]
* pong.hit(count += 1)
* }
*
* override def init = {
* ... // optional initialization on start
* }
*
* override def shutdown = {
* ... // optional cleanup on stop
* }
*
* ... // more life-cycle callbacks if needed
* }
*
* // create the ping actor
* val ping = TypedActor.newInstance(classOf[Ping], classOf[PingImpl])
*
* ping.hit(1) // use the actor
* ping.hit(1)
*
* // stop the actor
* TypedActor.stop(ping)
* </pre>
*
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
abstract class TypedActor extends Logging {
/**
* Holds RTTI (runtime type information) for the TypedActor, f.e. current 'sender'
* reference, the 'senderFuture' reference etc.
* <p/>
* This class does not contain static information but is updated by the runtime system
* at runtime.
* <p/>
* You can get a hold of the context using either the 'getContext()' or 'context'
* methods from the 'TypedActor' base class.
* <p/>
*
* Here is an example of usage (in Java):
* <pre>
* class PingImpl extends TypedActor implements Ping {
* public void hit(int count) {
* Pong pong = (Pong) getContext().getSender();
* pong.hit(count++);
* }
* }
* </pre>
*
* Here is an example of usage (in Scala):
* <pre>
* class PingImpl extends TypedActor with Ping {
* def hit(count: Int) = {
* val pong = context.sender.asInstanceOf[Pong]
* pong.hit(count += 1)
* }
* }
* </pre>
*/
@BeanProperty protected var context: TypedActorContext = _
/**
* The uuid for the Typed Actor.
*/
@BeanProperty @volatile var uuid = UUID.newUuid.toString
/**
* Identifier for actor, does not have to be a unique one. Default is the 'uuid'.
* <p/>
* This field is used for logging, AspectRegistry.actorsFor(id), identifier for remote
* actor in RemoteServer etc.But also as the identifier for persistence, which means
* that you can use a custom name to be able to retrieve the "correct" persisted state
* upon restart, remote restart etc.
* <p/>
* This property can be set to a custom ID.
*/
@BeanProperty @volatile protected var id: String = uuid
/**
* Defines the default timeout for '!!' and '!!!' invocations,
* e.g. the timeout for the future returned by the call to '!!' and '!!!'.
* <p/>
* This property can be set to a custom timeout.
*/
@BeanProperty @volatile protected var timeout: Long = Actor.TIMEOUT
/**
* User overridable callback.
* <p/>
* Is called when an Actor is started by invoking 'actor.start'.
*/
def init {}
/**
* User overridable callback.
* <p/>
* Is called when 'actor.stop' is invoked.
*/
def shutdown {}
/**
* User overridable callback.
* <p/>
* Is called on a crashed Actor right BEFORE it is restarted to allow clean up of resources before Actor is terminated.
*/
def preRestart(reason: Throwable) {}
/**
* User overridable callback.
* <p/>
* Is called right AFTER restart on the newly created Actor to allow reinitialization after an Actor crash.
*/
def postRestart(reason: Throwable) {}
/**
* User overridable callback.
* <p/>
* Is called during initialization. Can be used to initialize transactional state. Will be invoked within a transaction.
*/
def initTransactionalState {}
}
/**
* FIXME: document TypedTransactor
*
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
abstract class TypedTransactor extends TypedActor
/**
* Configuration factory for TypedActors.
*
* FIXDOC: document TypedActorConfiguration
*
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
final class TypedActorConfiguration {
private[akka] var _timeout: Long = Actor.TIMEOUT
private[akka] var _transactionRequired = false
private[akka] var _host: Option[InetSocketAddress] = None
private[akka] var _messageDispatcher: Option[MessageDispatcher] = None
def timeout = _timeout
def timeout(timeout: Duration) : TypedActorConfiguration = {
_timeout = timeout.toMillis
this
}
def makeTransactionRequired() : TypedActorConfiguration = {
_transactionRequired = true;
this
}
def makeRemote(hostname: String, port: Int) : TypedActorConfiguration = {
_host = Some(new InetSocketAddress(hostname, port))
this
}
def dispatcher(messageDispatcher: MessageDispatcher) : TypedActorConfiguration = {
_messageDispatcher = Some(messageDispatcher)
this
}
}
/**
* Holds RTTI (runtime type information) for the TypedActor, f.e. current 'sender'
* reference, the 'senderFuture' reference etc.
* <p/>
* This class does not contain static information but is updated by the runtime system
* at runtime.
* <p/>
* You can get a hold of the context using either the 'getContext()' or 'context'
* methods from the 'TypedActor' base class.
* <p/>
* Here is an example of usage (from Java):
* <pre>
* class PingImpl extends TypedActor implements Ping {
* public void hit(int count) {
* Pong pong = (Pong) getContext().getSender();
* pong.hit(count++);
* }
* }
* </pre>
*
* Here is an example of usage (in Scala):
* <pre>
* class PingImpl extends TypedActor with Ping {
* def hit(count: Int) = {
* val pong = context.sender.asInstanceOf[Pong]
* pong.hit(count += 1)
* }
* }
* </pre>
*
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
final class TypedActorContext {
private[akka] var _self: AnyRef = _
private[akka] var _sender: AnyRef = _
private[akka] var _senderFuture: CompletableFuture[Any] = _
/**
* Returns the current sender reference.
* Scala style getter.
*/
def sender: AnyRef = {
if (_sender eq null) throw new IllegalActorStateException("Sender reference should not be null.")
else _sender
}
/**
* Returns the current sender reference.
* Java style getter.
*/
def getSender: AnyRef = {
if (_sender eq null) throw new IllegalActorStateException("Sender reference should not be null.")
else _sender
}
/**
* Returns the current sender future TypedActor reference.
* Scala style getter.
*/
def senderFuture: Option[CompletableFuture[Any]] = if (_senderFuture eq null) None else Some(_senderFuture)
/**
* Returns the current sender future TypedActor reference.
* Java style getter.
* This method returns 'null' if the sender future is not available.
*/
def getSenderFuture = _senderFuture
}
/**
* Factory class for creating TypedActors out of plain POJOs and/or POJOs with interfaces.
*
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
object TypedActor extends Logging {
import Actor.actorOf
val AKKA_CAMEL_ROUTING_SCHEME = "akka".intern
private[actor] val AW_PROXY_PREFIX = "$$ProxiedByAW".intern
def newInstance[T](intfClass: Class[T], targetClass: Class[_], timeout: Long): T = {
newInstance(intfClass, newTypedActor(targetClass), actorOf(new Dispatcher(false)), None, timeout)
}
def newInstance[T](intfClass: Class[T], targetClass: Class[_]): T = {
newInstance(intfClass, newTypedActor(targetClass), actorOf(new Dispatcher(false)), None, Actor.TIMEOUT)
}
def newRemoteInstance[T](intfClass: Class[T], targetClass: Class[_], timeout: Long, hostname: String, port: Int): T = {
newInstance(intfClass, newTypedActor(targetClass), actorOf(new Dispatcher(false)), Some(new InetSocketAddress(hostname, port)), timeout)
}
def newRemoteInstance[T](intfClass: Class[T], targetClass: Class[_], hostname: String, port: Int): T = {
newInstance(intfClass, newTypedActor(targetClass), actorOf(new Dispatcher(false)), Some(new InetSocketAddress(hostname, port)), Actor.TIMEOUT)
}
def newInstance[T](intfClass: Class[T], targetClass: Class[_], config: TypedActorConfiguration): T = {
val actor = actorOf(new Dispatcher(config._transactionRequired))
if (config._messageDispatcher.isDefined) actor.dispatcher = config._messageDispatcher.get
newInstance(intfClass, newTypedActor(targetClass), actor, config._host, config.timeout)
}
private[akka] def newInstance[T](intfClass: Class[T], targetInstance: TypedActor, actorRef: ActorRef,
remoteAddress: Option[InetSocketAddress], timeout: Long): T = {
val context = injectTypedActorContext(targetInstance)
val proxy = Proxy.newInstance(Array(intfClass), Array(targetInstance), true, false)
actorRef.actor.asInstanceOf[Dispatcher].initialize(targetInstance.getClass, targetInstance, proxy, context)
actorRef.timeout = timeout
if (remoteAddress.isDefined) actorRef.makeRemote(remoteAddress.get)
AspectInitRegistry.register(proxy, AspectInit(intfClass, targetInstance, actorRef, remoteAddress, timeout))
actorRef.start
proxy.asInstanceOf[T]
}
// NOTE: currently not used - but keep it around
private[akka] def newInstance[T <: TypedActor](
targetClass: Class[T], actorRef: ActorRef, remoteAddress: Option[InetSocketAddress], timeout: Long): T = {
val proxy = {
val instance = Proxy.newInstance(targetClass, true, false)
if (instance.isInstanceOf[TypedActor]) instance.asInstanceOf[TypedActor]
else throw new IllegalActorStateException("Actor [" + targetClass.getName + "] is not a sub class of 'TypedActor'")
}
val context = injectTypedActorContext(proxy)
actorRef.actor.asInstanceOf[Dispatcher].initialize(targetClass, proxy, proxy, context)
actorRef.timeout = timeout
if (remoteAddress.isDefined) actorRef.makeRemote(remoteAddress.get)
AspectInitRegistry.register(proxy, AspectInit(targetClass, proxy, actorRef, remoteAddress, timeout))
actorRef.start
proxy.asInstanceOf[T]
}
/**
* Stops the current Typed Actor.
*/
def stop(proxy: AnyRef): Unit = AspectInitRegistry.initFor(proxy).actorRef.stop
/**
* Get the underlying dispatcher actor for the given Typed Actor.
*/
def actorFor(proxy: AnyRef): Option[ActorRef] =
ActorRegistry
.actorsFor(classOf[Dispatcher])
.find(a => a.actor.asInstanceOf[Dispatcher].proxy == proxy)
/**
* Links an other Typed Actor to this Typed Actor.
* @param supervisor the supervisor Typed Actor
* @param supervised the Typed Actor to link
*/
def link(supervisor: AnyRef, supervised: AnyRef) = {
val supervisorActor = actorFor(supervisor).getOrElse(
throw new IllegalActorStateException("Can't link when the supervisor is not an Typed Actor"))
val supervisedActor = actorFor(supervised).getOrElse(
throw new IllegalActorStateException("Can't link when the supervised is not an Typed Actor"))
supervisorActor.link(supervisedActor)
}
/**
* Links an other Typed Actor to this Typed Actor and sets the fault handling for the supervisor.
* @param supervisor the supervisor Typed Actor
* @param supervised the Typed Actor to link
* @param handler fault handling strategy
* @param trapExceptions array of exceptions that should be handled by the supervisor
*/
def link(supervisor: AnyRef, supervised: AnyRef,
handler: FaultHandlingStrategy, trapExceptions: Array[Class[_ <: Throwable]]) = {
val supervisorActor = actorFor(supervisor).getOrElse(
throw new IllegalActorStateException("Can't link when the supervisor is not an Typed Actor"))
val supervisedActor = actorFor(supervised).getOrElse(
throw new IllegalActorStateException("Can't link when the supervised is not an Typed Actor"))
supervisorActor.trapExit = trapExceptions.toList
supervisorActor.faultHandler = Some(handler)
supervisorActor.link(supervisedActor)
}
/**
* Unlink the supervised Typed Actor from the supervisor.
* @param supervisor the supervisor Typed Actor
* @param supervised the Typed Actor to unlink
*/
def unlink(supervisor: AnyRef, supervised: AnyRef) = {
val supervisorActor = actorFor(supervisor).getOrElse(
throw new IllegalActorStateException("Can't unlink when the supervisor is not an Typed Actor"))
val supervisedActor = actorFor(supervised).getOrElse(
throw new IllegalActorStateException("Can't unlink when the supervised is not an Typed Actor"))
supervisorActor.unlink(supervisedActor)
}
/**
* Sets the trap exit for the given supervisor Typed Actor.
* @param supervisor the supervisor Typed Actor
* @param trapExceptions array of exceptions that should be handled by the supervisor
*/
def trapExit(supervisor: AnyRef, trapExceptions: Array[Class[_ <: Throwable]]) = {
val supervisorActor = actorFor(supervisor).getOrElse(
throw new IllegalActorStateException("Can't set trap exceptions when the supervisor is not an Typed Actor"))
supervisorActor.trapExit = trapExceptions.toList
this
}
/**
* Sets the fault handling strategy for the given supervisor Typed Actor.
* @param supervisor the supervisor Typed Actor
* @param handler fault handling strategy
*/
def faultHandler(supervisor: AnyRef, handler: FaultHandlingStrategy) = {
val supervisorActor = actorFor(supervisor).getOrElse(
throw new IllegalActorStateException("Can't set fault handler when the supervisor is not an Typed Actor"))
supervisorActor.faultHandler = Some(handler)
this
}
private def injectTypedActorContext(typedActor: AnyRef): Option[TypedActorContext] = {
def injectTypedActorContext0(typedActor: AnyRef, clazz: Class[_]): Option[TypedActorContext] = {
val contextField = clazz.getDeclaredFields.toList.find(_.getType == classOf[TypedActorContext])
if (contextField.isDefined) {
contextField.get.setAccessible(true)
val context = new TypedActorContext
contextField.get.set(typedActor, context)
Some(context)
} else {
val parent = clazz.getSuperclass
if (parent != null) injectTypedActorContext0(typedActor, parent)
else {
log.ifTrace("Can't set 'TypedActorContext' for TypedActor [" +
typedActor.getClass.getName +
"] since no field of this type could be found.")
None
}
}
}
injectTypedActorContext0(typedActor, typedActor.getClass)
}
private[akka] def newTypedActor(targetClass: Class[_]): TypedActor = {
val instance = targetClass.newInstance
val typedActor =
if (instance.isInstanceOf[TypedActor]) instance.asInstanceOf[TypedActor]
else throw new IllegalArgumentException("Actor [" + targetClass.getName + "] is not a sub class of 'TypedActor'")
typedActor.init
import se.scalablesolutions.akka.stm.local.atomic
atomic {
typedActor.initTransactionalState
}
typedActor
}
private[akka] def supervise(restartStrategy: RestartStrategy, components: List[Supervise]): Supervisor =
Supervisor(SupervisorConfig(restartStrategy, components))
}
/**
* Internal helper class to help pass the contextual information between threads.
*
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
private[akka] object TypedActorContext {
import scala.util.DynamicVariable
private[actor] val sender = new DynamicVariable[AnyRef](null)
private[actor] val senderFuture = new DynamicVariable[CompletableFuture[Any]](null)
}
/**
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
private[akka] object AspectInitRegistry extends ListenerManagement {
private val initializations = new java.util.concurrent.ConcurrentHashMap[AnyRef, AspectInit]
def initFor(proxy: AnyRef) = initializations.get(proxy)
def register(proxy: AnyRef, init: AspectInit) = {
val res = initializations.put(proxy, init)
foreachListener(_ ! AspectInitRegistered(proxy, init))
res
}
def unregister(proxy: AnyRef) = {
val res = initializations.remove(proxy)
foreachListener(_ ! AspectInitUnregistered(proxy, res))
res
}
}
private[akka] sealed trait AspectInitRegistryEvent
private[akka] case class AspectInitRegistered(proxy: AnyRef, init: AspectInit) extends AspectInitRegistryEvent
private[akka] case class AspectInitUnregistered(proxy: AnyRef, init: AspectInit) extends AspectInitRegistryEvent
/**
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
private[akka] sealed case class AspectInit(
val interfaceClass: Class[_],
val targetInstance: TypedActor,
val actorRef: ActorRef,
val remoteAddress: Option[InetSocketAddress],
val timeout: Long) {
def this(interfaceClass: Class[_], targetInstance: TypedActor, actorRef: ActorRef, timeout: Long) =
this(interfaceClass, targetInstance, actorRef, None, timeout)
}
/**
* AspectWerkz Aspect that is turning POJO into TypedActor.
* <p/>
* Is deployed on a 'perInstance' basis with the pointcut 'execution(* *.*(..))',
* e.g. all methods on the instance.
*
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
@Aspect("perInstance")
private[akka] sealed class TypedActorAspect {
@volatile private var isInitialized = false
@volatile private var isStopped = false
private var interfaceClass: Class[_] = _
private var targetInstance: TypedActor = _
private var actorRef: ActorRef = _
private var remoteAddress: Option[InetSocketAddress] = _
private var timeout: Long = _
private var uuid: String = _
@volatile private var instance: TypedActor = _
@Around("execution(* *.*(..))")
def invoke(joinPoint: JoinPoint): AnyRef = {
if (!isInitialized) {
val init = AspectInitRegistry.initFor(joinPoint.getThis)
interfaceClass = init.interfaceClass
targetInstance = init.targetInstance
uuid = targetInstance.uuid
actorRef = init.actorRef
remoteAddress = init.remoteAddress
timeout = init.timeout
isInitialized = true
}
dispatch(joinPoint)
}
private def dispatch(joinPoint: JoinPoint) = {
if (remoteAddress.isDefined) remoteDispatch(joinPoint)
else localDispatch(joinPoint)
}
private def localDispatch(joinPoint: JoinPoint): AnyRef = {
val rtti = joinPoint.getRtti.asInstanceOf[MethodRtti]
val isOneWay = isVoid(rtti)
val sender = TypedActorContext.sender.value
val senderFuture = TypedActorContext.senderFuture.value
if (!actorRef.isRunning && !isStopped) {
isStopped = true
joinPoint.proceed
} else if (isOneWay) {
actorRef ! Invocation(joinPoint, true, true, sender, senderFuture)
null.asInstanceOf[AnyRef]
} else {
val result = (actorRef !! (Invocation(joinPoint, false, isOneWay, sender, senderFuture), timeout)).as[AnyRef]
if (result.isDefined) result.get
else throw new IllegalActorStateException("No result defined for invocation [" + joinPoint + "]")
}
}
private def remoteDispatch(joinPoint: JoinPoint): AnyRef = {
val rtti = joinPoint.getRtti.asInstanceOf[MethodRtti]
val isOneWay = isVoid(rtti)
val (message: Array[AnyRef], isEscaped) = escapeArguments(rtti.getParameterValues)
val typedActorInfo = TypedActorInfoProtocol.newBuilder
.setInterface(interfaceClass.getName)
.setMethod(rtti.getMethod.getName)
.build
val actorInfo = ActorInfoProtocol.newBuilder
.setUuid(uuid)
.setTarget(targetInstance.getClass.getName)
.setTimeout(timeout)
.setActorType(ActorType.TYPED_ACTOR)
.setTypedActorInfo(typedActorInfo)
.build
val requestBuilder = RemoteRequestProtocol.newBuilder
.setId(RemoteRequestProtocolIdFactory.nextId)
.setMessage(MessageSerializer.serialize(message))
.setActorInfo(actorInfo)
.setIsOneWay(isOneWay)
val id = actorRef.registerSupervisorAsRemoteActor
if (id.isDefined) requestBuilder.setSupervisorUuid(id.get)
val remoteMessage = requestBuilder.build
val future = RemoteClient.clientFor(remoteAddress.get).send(remoteMessage, None)
if (isOneWay) null // for void methods
else {
if (future.isDefined) {
future.get.await
val result = getResultOrThrowException(future.get)
if (result.isDefined) result.get
else throw new IllegalActorStateException("No result returned from call to [" + joinPoint + "]")
} else throw new IllegalActorStateException("No future returned from call to [" + joinPoint + "]")
}
}
private def getResultOrThrowException[T](future: Future[T]): Option[T] =
if (future.exception.isDefined) {
val (_, cause) = future.exception.get
throw cause
} else future.result
private def isVoid(rtti: MethodRtti) = rtti.getMethod.getReturnType == java.lang.Void.TYPE
private def escapeArguments(args: Array[AnyRef]): Tuple2[Array[AnyRef], Boolean] = {
var isEscaped = false
val escapedArgs = for (arg <- args) yield {
val clazz = arg.getClass
if (clazz.getName.contains(TypedActor.AW_PROXY_PREFIX)) {
isEscaped = true
TypedActor.AW_PROXY_PREFIX + clazz.getSuperclass.getName
} else arg
}
(escapedArgs, isEscaped)
}
}
/**
* Represents a snapshot of the current invocation.
*
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
@serializable private[akka] case class Invocation(
joinPoint: JoinPoint, isOneWay: Boolean, isVoid: Boolean, sender: AnyRef, senderFuture: CompletableFuture[Any]) {
override def toString: String = synchronized {
"Invocation [" +
"\n\t\tmethod = " + joinPoint.getRtti.asInstanceOf[MethodRtti].getMethod.getName + " @ " + joinPoint.getTarget.getClass.getName +
"\n\t\tisOneWay = " + isOneWay +
"\n\t\tisVoid = " + isVoid +
"\n\t\tsender = " + sender +
"\n\t\tsenderFuture = " + senderFuture +
"]"
}
override def hashCode: Int = synchronized {
var result = HashCode.SEED
result = HashCode.hash(result, joinPoint)
result = HashCode.hash(result, isOneWay)
result = HashCode.hash(result, isVoid)
result = HashCode.hash(result, sender)
result = HashCode.hash(result, senderFuture)
result
}
override def equals(that: Any): Boolean = synchronized {
that != null &&
that.isInstanceOf[Invocation] &&
that.asInstanceOf[Invocation].joinPoint == joinPoint &&
that.asInstanceOf[Invocation].isOneWay == isOneWay &&
that.asInstanceOf[Invocation].isVoid == isVoid &&
that.asInstanceOf[Invocation].sender == sender &&
that.asInstanceOf[Invocation].senderFuture == senderFuture
}
}
object Dispatcher {
val ZERO_ITEM_CLASS_ARRAY = Array[Class[_]]()
val ZERO_ITEM_OBJECT_ARRAY = Array[Object]()
}
/**
* Generic Actor managing Invocation dispatch, transaction and error management.
*
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
private[akka] class Dispatcher(transactionalRequired: Boolean) extends Actor {
import Dispatcher._
private[actor] var proxy: AnyRef = _
private var context: Option[TypedActorContext] = None
private var targetClass: Class[_] = _
@volatile private[akka] var targetInstance: TypedActor = _
private var proxyDelegate: Field = _
private[actor] def initialize(
targetClass: Class[_], targetInstance: TypedActor, proxy: AnyRef, ctx: Option[TypedActorContext]) = {
if (transactionalRequired || isTransactional(targetClass)) self.makeTransactionRequired
self.id = targetClass.getName
this.targetClass = targetClass
this.proxy = proxy
this.targetInstance = targetInstance
this.context = ctx
proxyDelegate = {
val field = proxy.getClass.getDeclaredField("DELEGATE_0")
field.setAccessible(true)
field
}
if (self.lifeCycle.isEmpty) self.lifeCycle = Some(LifeCycle(Permanent))
}
def receive = {
case invocation @ Invocation(joinPoint, isOneWay, _, sender, senderFuture) =>
TypedActor.log.ifTrace("Invoking Typed Actor with message:\n" + invocation)
context.foreach { ctx =>
if (sender ne null) ctx._sender = sender
if (senderFuture ne null) ctx._senderFuture = senderFuture
}
TypedActorContext.sender.value = joinPoint.getThis // set next sender
self.senderFuture.foreach(TypedActorContext.senderFuture.value = _)
if (Actor.SERIALIZE_MESSAGES) serializeArguments(joinPoint)
if (isOneWay) joinPoint.proceed
else self.reply(joinPoint.proceed)
// Jan Kronquist: started work on issue 121
case Link(proxy) => self.link(proxy)
case Unlink(proxy) => self.unlink(proxy)
case unexpected => throw new IllegalActorStateException(
"Unexpected message [" + unexpected + "] sent to [" + this + "]")
}
override def preRestart(reason: Throwable) {
targetInstance.preRestart(reason)
// rewrite target instance in Dispatcher and AspectWerkz Proxy
targetInstance = TypedActor.newTypedActor(targetClass)
proxyDelegate.set(proxy, targetInstance)
}
override def postRestart(reason: Throwable) {
targetInstance.postRestart(reason)
}
override def shutdown {
targetInstance.shutdown
AspectInitRegistry.unregister(proxy);
}
override def initTransactionalState {
targetInstance.initTransactionalState
}
def isTransactional(clazz: Class[_]): Boolean =
if (clazz == null) false
else if (clazz.isAssignableFrom(classOf[TypedTransactor])) true
else isTransactional(clazz.getSuperclass)
private def serializeArguments(joinPoint: JoinPoint) = {
val args = joinPoint.getRtti.asInstanceOf[MethodRtti].getParameterValues
var unserializable = false
var hasMutableArgument = false
for (arg <- args.toList) {
if (!arg.isInstanceOf[String] &&
!arg.isInstanceOf[Byte] &&
!arg.isInstanceOf[Int] &&
!arg.isInstanceOf[Long] &&
!arg.isInstanceOf[Float] &&
!arg.isInstanceOf[Double] &&
!arg.isInstanceOf[Boolean] &&
!arg.isInstanceOf[Char] &&
!arg.isInstanceOf[java.lang.Byte] &&
!arg.isInstanceOf[java.lang.Integer] &&
!arg.isInstanceOf[java.lang.Long] &&
!arg.isInstanceOf[java.lang.Float] &&
!arg.isInstanceOf[java.lang.Double] &&
!arg.isInstanceOf[java.lang.Boolean] &&
!arg.isInstanceOf[java.lang.Character]) {
hasMutableArgument = true
}
if (arg.getClass.getName.contains(TypedActor.AW_PROXY_PREFIX)) unserializable = true
}
if (!unserializable && hasMutableArgument) {
val copyOfArgs = Serializer.Java.deepClone(args)
joinPoint.getRtti.asInstanceOf[MethodRtti].setParameterValues(copyOfArgs.asInstanceOf[Array[AnyRef]])
}
}
}

View file

@ -0,0 +1,580 @@
/**
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
*/
package se.scalablesolutions.akka.actor
import se.scalablesolutions.akka.dispatch._
import se.scalablesolutions.akka.stm.global._
import se.scalablesolutions.akka.config.{AllForOneStrategy, OneForOneStrategy, FaultHandlingStrategy}
import se.scalablesolutions.akka.config.ScalaConfig._
import java.net.InetSocketAddress
/**
* Subclass this abstract class to create a MDB-style untyped actor.
* <p/>
* This class is meant to be used from Java.
* <p/>
* Here is an example on how to create and use an UntypedActor:
* <pre>
* public class SampleUntypedActor extends UntypedActor {
* public void onReceive(Object message, UntypedActorRef self) throws Exception {
* if (message instanceof String) {
* String msg = (String)message;
*
* if (msg.equals("UseReply")) {
* // Reply to original sender of message using the 'replyUnsafe' method
* self.replyUnsafe(msg + ":" + self.getUuid());
*
* } else if (msg.equals("UseSender") && self.getSender().isDefined()) {
* // Reply to original sender of message using the sender reference
* // also passing along my own refererence (the self)
* self.getSender().get().sendOneWay(msg, self);
*
* } else if (msg.equals("UseSenderFuture") && self.getSenderFuture().isDefined()) {
* // Reply to original sender of message using the sender future reference
* self.getSenderFuture().get().completeWithResult(msg);
*
* } else if (msg.equals("SendToSelf")) {
* // Send message to the actor itself recursively
* self.sendOneWay(msg)
*
* } else if (msg.equals("ForwardMessage")) {
* // Retreive an actor from the ActorRegistry by ID and get an ActorRef back
* ActorRef actorRef = ActorRegistry.actorsFor("some-actor-id").head();
* // Wrap the ActorRef in an UntypedActorRef and forward the message to this actor
* UntypedActorRef.wrap(actorRef).forward(msg, self);
*
* } else throw new IllegalArgumentException("Unknown message: " + message);
* } else throw new IllegalArgumentException("Unknown message: " + message);
* }
*
* public static void main(String[] args) {
* UntypedActorRef actor = UntypedActor.actorOf(SampleUntypedActor.class);
* actor.start();
* actor.sendOneWay("SendToSelf");
* actor.stop();
* }
* }
* </pre>
*
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
abstract class UntypedActor extends Actor {
protected[akka] var context: Option[UntypedActorRef] = None
final protected def receive = {
case msg =>
if (context.isEmpty) {
val ctx = new UntypedActorRef(self)
context = Some(ctx)
onReceive(msg, ctx)
} else onReceive(msg, context.get)
}
@throws(classOf[Exception])
def onReceive(message: Any, context: UntypedActorRef): Unit
}
/**
* Implements the Transactor abstraction. E.g. a transactional UntypedActor.
*
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
abstract class UntypedTransactor extends UntypedActor {
self.makeTransactionRequired
}
/**
* Extend this abstract class to create a remote UntypedActor.
*
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
abstract class RemoteUntypedActor(address: InetSocketAddress) extends UntypedActor {
def this(hostname: String, port: Int) = this(new InetSocketAddress(hostname, port))
self.makeRemote(address)
}
/**
* Factory object for creating and managing 'UntypedActor's. Meant to be used from Java.
* <p/>
* Example on how to create an actor:
* <pre>
* ActorRef actor = UntypedActor.actorOf(MyUntypedActor.class);
* actor.start();
* actor.sendOneWay(message, context)
* actor.stop();
* </pre>
* You can create and start the actor in one statement like this:
* <pre>
* ActorRef actor = UntypedActor.actorOf(MyUntypedActor.class).start();
* </pre>
*
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
object UntypedActor {
/**
* Creates an ActorRef out of the Actor. Allows you to pass in the class for the Actor.
* <p/>
* Example in Java:
* <pre>
* ActorRef actor = UntypedActor.actorOf(MyUntypedActor.class);
* actor.start();
* actor.sendOneWay(message, context)
* actor.stop();
* </pre>
* You can create and start the actor in one statement like this:
* <pre>
* ActorRef actor = UntypedActor.actorOf(MyUntypedActor.class).start();
* </pre>
*/
def actorOf(clazz: Class[_]): UntypedActorRef = {
if (!clazz.isInstanceOf[Class[_ <: UntypedActor]]) throw new IllegalArgumentException(
"Class [" + clazz.getName + "] passed into the 'actorOf' factory method needs to be assignable from 'UntypedActor'")
UntypedActorRef.wrap(new LocalActorRef(() => clazz.newInstance.asInstanceOf[Actor]))
}
/**
* NOTE: Use this convenience method with care, do NOT make it possible to get a reference to the
* UntypedActor instance directly, but only through its 'UntypedActorRef' wrapper reference.
* <p/>
* Creates an ActorRef out of the Actor. Allows you to pass in the instance for the Actor. Only
* use this method when you need to pass in constructor arguments into the 'UntypedActor'.
* <p/>
* Example in Java:
* <pre>
* ActorRef actor = UntypedActor.actorOf(new MyUntypedActor("service:name", 5));
* actor.start();
* actor.sendOneWay(message, context)
* actor.stop();
* </pre>
* You can create and start the actor in one statement like this:
* <pre>
* ActorRef actor = UntypedActor.actorOf(MyUntypedActor.class).start();
* </pre>
*/
def actorOf(actorInstance: UntypedActor): UntypedActorRef = UntypedActorRef.wrap(new LocalActorRef(() => actorInstance))
}
/**
* Use this class if you need to wrap an 'ActorRef' in the more Java-friendly 'UntypedActorRef'.
*
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
object UntypedActorRef {
def wrap(actorRef: ActorRef) = new UntypedActorRef(actorRef)
}
/**
* A Java-friendly wrapper class around the 'ActorRef'.
*
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
class UntypedActorRef(val actorRef: ActorRef) {
/**
* Returns the uuid for the actor.
*/
def getUuid(): String = actorRef.uuid
/**
* Identifier for actor, does not have to be a unique one. Default is the 'uuid'.
* <p/>
* This field is used for logging, AspectRegistry.actorsFor(id), identifier for remote
* actor in RemoteServer etc.But also as the identifier for persistence, which means
* that you can use a custom name to be able to retrieve the "correct" persisted state
* upon restart, remote restart etc.
*/
def setId(id: String) = actorRef.id = id
def getId(): String = actorRef.id
/**
* Defines the default timeout for '!!' and '!!!' invocations,
* e.g. the timeout for the future returned by the call to '!!' and '!!!'.
*/
def setTimeout(timeout: Long) = actorRef.timeout = timeout
def getTimeout(): Long = actorRef.timeout
/**
* Defines the default timeout for an initial receive invocation.
* When specified, the receive function should be able to handle a 'ReceiveTimeout' message.
*/
def setReceiveTimeout(timeout: Long) = actorRef.receiveTimeout = Some(timeout)
def getReceiveTimeout(): Option[Long] = actorRef.receiveTimeout
/**
* Set 'trapExit' to the list of exception classes that the actor should be able to trap
* from the actor it is supervising. When the supervising actor throws these exceptions
* then they will trigger a restart.
* <p/>
*
* Trap all exceptions:
* <pre>
* context.setTrapExit(new Class[]{Throwable.class});
* </pre>
*
* Trap specific exceptions only:
* <pre>
* context.setTrapExit(new Class[]{MyApplicationException.class, MyApplicationError.class});
* </pre>
*/
def setTrapExit(exceptions: Array[Class[_ <: Throwable]]) = actorRef.trapExit = exceptions.toList
def getTrapExit(): Array[Class[_ <: Throwable]] = actorRef.trapExit.toArray
/**
* If 'trapExit' is set for the actor to act as supervisor, then a 'faultHandler' must be defined.
* <p/>
* Can be one of:
* <pre>
* context.setFaultHandler(new AllForOneStrategy(maxNrOfRetries, withinTimeRange));
* </pre>
* Or:
* <pre>
* context.setFaultHandler(new OneForOneStrategy(maxNrOfRetries, withinTimeRange));
* </pre>
*/
def setFaultHandler(handler: FaultHandlingStrategy) = actorRef.faultHandler = Some(handler)
def getFaultHandler(): Option[FaultHandlingStrategy] = actorRef.faultHandler
/**
* Defines the life-cycle for a supervised actor.
*/
def setLifeCycle(lifeCycle: LifeCycle) = actorRef.lifeCycle = Some(lifeCycle)
def getLifeCycle(): Option[LifeCycle] = actorRef.lifeCycle
/**
* The default dispatcher is the <tt>Dispatchers.globalExecutorBasedEventDrivenDispatcher();</tt>.
* This means that all actors will share the same event-driven executor based dispatcher.
* <p/>
* You can override it so it fits the specific use-case that the actor is used for.
* See the <tt>se.scalablesolutions.akka.dispatch.Dispatchers</tt> class for the different
* dispatchers available.
* <p/>
* The default is also that all actors that are created and spawned from within this actor
* is sharing the same dispatcher as its creator.
*/
def setDispatcher(dispatcher: MessageDispatcher) = actorRef.dispatcher = dispatcher
def getDispatcher(): MessageDispatcher = actorRef.dispatcher
/**
* The reference sender Actor of the last received message.
* Is defined if the message was sent from another Actor, else None.
*/
def getSender(): Option[UntypedActorRef] = actorRef.sender match {
case Some(s) => Some(UntypedActorRef.wrap(s))
case None => None
}
/**
* The reference sender future of the last received message.
* Is defined if the message was sent with sent with 'sendRequestReply' or 'sendRequestReplyFuture', else None.
*/
def getSenderFuture(): Option[CompletableFuture[Any]] = actorRef.senderFuture
/**
* Starts up the actor and its message queue.
*/
def start(): UntypedActorRef = UntypedActorRef.wrap(actorRef.start)
/**
* Shuts down the actor its dispatcher and message queue.
* Alias for 'stop'.
*/
def exit() = stop()
/**
* Shuts down the actor its dispatcher and message queue.
*/
def stop(): Unit = actorRef.stop()
/**
* Sends a one-way asynchronous message. E.g. fire-and-forget semantics.
* <p/>
* <pre>
* actor.sendOneWay(message);
* </pre>
* <p/>
*/
def sendOneWay(message: AnyRef) = actorRef.!(message)(None)
/**
* Sends a one-way asynchronous message. E.g. fire-and-forget semantics.
* <p/>
* Allows you to pass along the sender of the messag.
* <p/>
* <pre>
* actor.sendOneWay(message, context);
* </pre>
* <p/>
*/
def sendOneWay(message: AnyRef, sender: UntypedActorRef) =
if (sender eq null) actorRef.!(message)(None)
else actorRef.!(message)(Some(sender.actorRef))
/**
* Sends a message asynchronously and waits on a future for a reply message under the hood. The timeout is taken from
* the default timeout in the Actor.
* <p/>
* It waits on the reply either until it receives it or until the timeout expires
* (which will throw an ActorTimeoutException). E.g. send-and-receive-eventually semantics.
* <p/>
* <b>NOTE:</b>
* Use this method with care. In most cases it is better to use 'sendOneWay' together with 'context.getSender()' to
* implement request/response message exchanges.
* <p/>
* If you are sending messages using <code>sendRequestReply</code> then you <b>have to</b> use <code>context.reply(..)</code>
* to send a reply message to the original sender. If not then the sender will block until the timeout expires.
*/
def sendRequestReply(message: AnyRef): AnyRef =
actorRef.!!(message)(None).getOrElse(throw new ActorTimeoutException(
"Message [" + message +
"]\n\tsent to [" + actorRef.actorClassName +
"]\n\twith timeout [" + actorRef.timeout +
"]\n\ttimed out."))
.asInstanceOf[AnyRef]
/**
* Sends a message asynchronously and waits on a future for a reply message under the hood. The timeout is taken from
* the default timeout in the Actor.
* <p/>
* It waits on the reply either until it receives it or until the timeout expires
* (which will throw an ActorTimeoutException). E.g. send-and-receive-eventually semantics.
* <p/>
* <b>NOTE:</b>
* Use this method with care. In most cases it is better to use 'sendOneWay' together with 'context.getSender()' to
* implement request/response message exchanges.
* <p/>
* If you are sending messages using <code>sendRequestReply</code> then you <b>have to</b> use <code>context.reply(..)</code>
* to send a reply message to the original sender. If not then the sender will block until the timeout expires.
*/
def sendRequestReply(message: AnyRef, sender: UntypedActorRef): AnyRef = {
val result = if (sender eq null) actorRef.!!(message)(None)
else actorRef.!!(message)(Some(sender.actorRef))
result.getOrElse(throw new ActorTimeoutException(
"Message [" + message +
"]\n\tsent to [" + actorRef.actorClassName +
"]\n\tfrom [" + sender.actorRef.actorClassName +
"]\n\twith timeout [" + actorRef.timeout +
"]\n\ttimed out."))
.asInstanceOf[AnyRef]
}
/**
* Sends a message asynchronously and waits on a future for a reply message under the hood.
* <p/>
* It waits on the reply either until it receives it or until the timeout expires
* (which will throw an ActorTimeoutException). E.g. send-and-receive-eventually semantics.
* <p/>
* <b>NOTE:</b>
* Use this method with care. In most cases it is better to use 'sendOneWay' together with 'context.getSender()' to
* implement request/response message exchanges.
* <p/>
* If you are sending messages using <code>sendRequestReply</code> then you <b>have to</b> use <code>context.reply(..)</code>
* to send a reply message to the original sender. If not then the sender will block until the timeout expires.
*/
def sendRequestReply(message: AnyRef, timeout: Long): AnyRef =
actorRef.!!(message, timeout)(None).getOrElse(throw new ActorTimeoutException(
"Message [" + message +
"]\n\tsent to [" + actorRef.actorClassName +
"]\n\twith timeout [" + timeout +
"]\n\ttimed out."))
.asInstanceOf[AnyRef]
/**
* Sends a message asynchronously and waits on a future for a reply message under the hood.
* <p/>
* It waits on the reply either until it receives it or until the timeout expires
* (which will throw an ActorTimeoutException). E.g. send-and-receive-eventually semantics.
* <p/>
* <b>NOTE:</b>
* Use this method with care. In most cases it is better to use 'sendOneWay' together with 'context.getSender()' to
* implement request/response message exchanges.
* <p/>
* If you are sending messages using <code>sendRequestReply</code> then you <b>have to</b> use <code>context.reply(..)</code>
* to send a reply message to the original sender. If not then the sender will block until the timeout expires.
*/
def sendRequestReply(message: AnyRef, timeout: Long, sender: UntypedActorRef): AnyRef = {
val result = if (sender eq null) actorRef.!!(message, timeout)(None)
else actorRef.!!(message)(Some(sender.actorRef))
result.getOrElse(throw new ActorTimeoutException(
"Message [" + message +
"]\n\tsent to [" + actorRef.actorClassName +
"]\n\tfrom [" + sender.actorRef.actorClassName +
"]\n\twith timeout [" + timeout +
"]\n\ttimed out."))
.asInstanceOf[AnyRef]
}
/**
* Sends a message asynchronously returns a future holding the eventual reply message. The timeout is taken from
* the default timeout in the Actor.
* <p/>
* <b>NOTE:</b>
* Use this method with care. In most cases it is better to use 'sendOneWay' together with the 'context.getSender()' to
* implement request/response message exchanges.
* <p/>
* If you are sending messages using <code>sendRequestReplyFuture</code> then you <b>have to</b> use <code>context.reply(..)</code>
* to send a reply message to the original sender. If not then the sender will block until the timeout expires.
*/
def sendRequestReplyFuture(message: AnyRef): Future[_] = actorRef.!!!(message)(None)
/**
* Sends a message asynchronously returns a future holding the eventual reply message. The timeout is taken from
* the default timeout in the Actor.
* <p/>
* <b>NOTE:</b>
* Use this method with care. In most cases it is better to use 'sendOneWay' together with the 'context.getSender()' to
* implement request/response message exchanges.
* <p/>
* If you are sending messages using <code>sendRequestReplyFuture</code> then you <b>have to</b> use <code>context.reply(..)</code>
* to send a reply message to the original sender. If not then the sender will block until the timeout expires.
*/
def sendRequestReplyFuture(message: AnyRef, sender: UntypedActorRef): Future[_] =
if (sender eq null) actorRef.!!!(message)(None)
else actorRef.!!!(message)(Some(sender.actorRef))
/**
* Sends a message asynchronously returns a future holding the eventual reply message.
* <p/>
* <b>NOTE:</b>
* Use this method with care. In most cases it is better to use 'sendOneWay' together with the 'context.getSender()' to
* implement request/response message exchanges.
* <p/>
* If you are sending messages using <code>sendRequestReplyFuture</code> then you <b>have to</b> use <code>context.reply(..)</code>
* to send a reply message to the original sender. If not then the sender will block until the timeout expires.
*/
def sendRequestReplyFuture(message: AnyRef, timeout: Long): Future[_] = actorRef.!!!(message, timeout)(None)
/**
* Sends a message asynchronously returns a future holding the eventual reply message.
* <p/>
* <b>NOTE:</b>
* Use this method with care. In most cases it is better to use 'sendOneWay' together with the 'context.getSender()' to
* implement request/response message exchanges.
* <p/>
* If you are sending messages using <code>sendRequestReplyFuture</code> then you <b>have to</b> use <code>context.reply(..)</code>
* to send a reply message to the original sender. If not then the sender will block until the timeout expires.
*/
def sendRequestReplyFuture(message: AnyRef, timeout: Long, sender: UntypedActorRef): Future[_] =
if (sender eq null) actorRef.!!!(message, timeout)(None)
else actorRef.!!!(message)(Some(sender.actorRef))
/**
* Forwards the message and passes the original sender actor as the sender.
* <p/>
* Works with 'sendOneWay', 'sendRequestReply' and 'sendRequestReplyFuture'.
*/
def forward(message: AnyRef, sender: UntypedActorRef): Unit =
if (sender eq null) throw new IllegalArgumentException("The 'sender' argument to 'forward' can't be null")
else actorRef.forward(message)(Some(sender.actorRef))
/**
* Use <code>context.replyUnsafe(..)</code> to reply with a message to the original sender of the message currently
* being processed.
* <p/>
* Throws an IllegalStateException if unable to determine what to reply to.
*/
def replyUnsafe(message: AnyRef): Unit = actorRef.reply(message)
/**
* Use <code>context.replySafe(..)</code> to reply with a message to the original sender of the message currently
* being processed.
* <p/>
* Returns true if reply was sent, and false if unable to determine what to reply to.
*/
def replySafe(message: AnyRef): Boolean = actorRef.reply_?(message)
/**
* Returns the class for the Actor instance that is managed by the ActorRef.
*/
def getActorClass(): Class[_ <: Actor] = actorRef.actorClass
/**
* Returns the class name for the Actor instance that is managed by the ActorRef.
*/
def getActorClassName(): String = actorRef.actorClassName
/**
* Invoking 'makeRemote' means that an actor will be moved to and invoked on a remote host.
*/
def makeRemote(hostname: String, port: Int): Unit = actorRef.makeRemote(hostname, port)
/**
* Invoking 'makeRemote' means that an actor will be moved to and invoked on a remote host.
*/
def makeRemote(address: InetSocketAddress): Unit = actorRef.makeRemote(address)
/**
* Invoking 'makeTransactionRequired' means that the actor will **start** a new transaction if non exists.
* However, it will always participate in an existing transaction.
*/
def makeTransactionRequired(): Unit = actorRef.makeTransactionRequired
/**
* Sets the transaction configuration for this actor. Needs to be invoked before the actor is started.
*/
def setTransactionConfig(config: TransactionConfig): Unit = actorRef.transactionConfig = config
/**
* Get the transaction configuration for this actor.
*/
def getTransactionConfig(): TransactionConfig = actorRef.transactionConfig
/**
* Gets the remote address for the actor, if any, else None.
*/
def getRemoteAddress(): Option[InetSocketAddress] = actorRef.remoteAddress
/**
* Returns the home address and port for this actor.
*/
def getHomeAddress(): InetSocketAddress = actorRef.homeAddress
/**
* Set the home address and port for this actor.
*/
def setHomeAddress(hostnameAndPort: Tuple2[String, Int]): Unit = actorRef.homeAddress = hostnameAndPort
/**
* Set the home address and port for this actor.
*/
def setHomeAddress(address: InetSocketAddress): Unit = actorRef.homeAddress = address
/**
* Links an other actor to this actor. Links are unidirectional and means that a the linking actor will
* receive a notification if the linked actor has crashed.
* <p/>
* If the 'trapExit' member field has been set to at contain at least one exception class then it will
* 'trap' these exceptions and automatically restart the linked actors according to the restart strategy
* defined by the 'faultHandler'.
*/
def link(actor: UntypedActorRef): Unit = actorRef.link(actor.actorRef)
/**
* Unlink the actor.
*/
def unlink(actor: UntypedActorRef): Unit = actorRef.unlink(actor.actorRef)
/**
* Atomically start and link an actor.
*/
def startLink(actor: UntypedActorRef): Unit = actorRef.startLink(actor.actorRef)
/**
* Atomically start, link and make an actor remote.
*/
def startLinkRemote(actor: UntypedActorRef, hostname: String, port: Int): Unit =
actorRef.startLinkRemote(actor.actorRef, hostname, port)
/**
* Returns the mailbox size.
*/
def getMailboxSize(): Int = actorRef.mailboxSize
/**
* Returns the current supervisor if there is one, null if not.
*/
def getSupervisor(): UntypedActorRef = UntypedActorRef.wrap(actorRef.supervisor.getOrElse(null))
}

View file

@ -82,6 +82,8 @@ object Config extends Logging {
if (VERSION != CONFIG_VERSION) throw new ConfigurationException(
"Akka JAR version [" + VERSION + "] is different than the provided config ('akka.conf') version [" + CONFIG_VERSION + "]")
val TIME_UNIT = config.getString("akka.time-unit", "seconds")
val startTime = System.currentTimeMillis
def uptime = (System.currentTimeMillis - startTime) / 1000
}

View file

@ -5,7 +5,7 @@
package se.scalablesolutions.akka.config
/*
import se.scalablesolutions.akka.kernel.{ActiveObject, ActiveObjectProxy}
import se.scalablesolutions.akka.kernel.{TypedActor, TypedActorProxy}
import com.google.inject.{AbstractModule}
import java.util.{List => JList, ArrayList}
import scala.reflect.BeanProperty
@ -55,6 +55,6 @@ class Component(@BeanProperty val intf: Class[_],
@BeanProperty val target: Class[_],
@BeanProperty val lifeCycle: LifeCycle,
@BeanProperty val timeout: Int) extends Server {
def newWorker(proxy: ActiveObjectProxy) = se.scalablesolutions.akka.kernel.Supervise(proxy.server, lifeCycle.transform)
def newWorker(proxy: TypedActorProxy) = se.scalablesolutions.akka.kernel.Supervise(proxy.server, lifeCycle.transform)
}
*/

View file

@ -6,14 +6,14 @@ package se.scalablesolutions.akka.config
import ScalaConfig.{RestartStrategy, Component}
private[akka] trait ActiveObjectConfiguratorBase {
private[akka] trait TypedActorConfiguratorBase {
def getExternalDependency[T](clazz: Class[T]): T
def configure(restartStrategy: RestartStrategy, components: List[Component]): ActiveObjectConfiguratorBase
def configure(restartStrategy: RestartStrategy, components: List[Component]): TypedActorConfiguratorBase
def inject: ActiveObjectConfiguratorBase
def inject: TypedActorConfiguratorBase
def supervise: ActiveObjectConfiguratorBase
def supervise: TypedActorConfiguratorBase
def reset

View file

@ -42,16 +42,7 @@ object ScalaConfig {
case object AllForOne extends FailOverScheme
case object OneForOne extends FailOverScheme
case class LifeCycle(scope: Scope,
restartCallbacks: Option[RestartCallbacks] = None,
shutdownCallback: Option[ShutdownCallback] = None) extends ConfigElement
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 class ShutdownCallback(shutdown: String) {
if (shutdown eq null) throw new IllegalArgumentException("Shutdown callback method can't be null")
}
case class LifeCycle(scope: Scope) extends ConfigElement
case object Permanent extends Scope
case object Temporary extends Scope
@ -137,26 +128,12 @@ object JavaConfig {
scheme.transform, maxNrOfRetries, withinTimeRange, trapExceptions.toList)
}
class LifeCycle(@BeanProperty val scope: Scope,
@BeanProperty val restartCallbacks: RestartCallbacks,
@BeanProperty val shutdownCallback: ShutdownCallback) extends ConfigElement {
def this(scope: Scope) = this(scope, null, null)
def this(scope: Scope, restartCallbacks: RestartCallbacks) = this(scope, restartCallbacks, null)
def this(scope: Scope, shutdownCallback: ShutdownCallback) = this(scope, null, shutdownCallback)
class LifeCycle(@BeanProperty val scope: Scope) extends ConfigElement {
def transform = {
val restartCallbacksOption = if (restartCallbacks eq null) None else Some(restartCallbacks.transform)
val shutdownCallbackOption = if (shutdownCallback eq null) None else Some(shutdownCallback.transform)
se.scalablesolutions.akka.config.ScalaConfig.LifeCycle(scope.transform, restartCallbacksOption, shutdownCallbackOption)
se.scalablesolutions.akka.config.ScalaConfig.LifeCycle(scope.transform)
}
}
class RestartCallbacks(@BeanProperty val preRestart: String, @BeanProperty val postRestart: String) {
def transform = se.scalablesolutions.akka.config.ScalaConfig.RestartCallbacks(preRestart, postRestart)
}
class ShutdownCallback(@BeanProperty val shutdown: String) {
def transform = se.scalablesolutions.akka.config.ScalaConfig.ShutdownCallback(shutdown)
}
abstract class Scope extends ConfigElement {
def transform: se.scalablesolutions.akka.config.ScalaConfig.Scope
}

View file

@ -12,54 +12,55 @@ import java.util.{ArrayList}
import com.google.inject._
/**
* Configurator for the Active Objects. Used to do declarative configuration of supervision.
* It also does dependency injection with and into Active Objects using dependency injection
* Configurator for the TypedActors. Used to do declarative configuration of supervision.
* It also does dependency injection with and into TypedActors using dependency injection
* frameworks such as Google Guice or Spring.
* <p/>
* If you don't want declarative configuration then you should use the <code>ActiveObject</code>
* If you don't want declarative configuration then you should use the <code>TypedActor</code>
* factory methods.
*
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
class ActiveObjectConfigurator {
class TypedActorConfigurator {
import scala.collection.JavaConversions._
// TODO: make pluggable once we have f.e a SpringConfigurator
private val INSTANCE = new ActiveObjectGuiceConfigurator
private val INSTANCE = new TypedActorGuiceConfigurator
/**
* Returns the a list with all active objects that has been put under supervision for the class specified.
* Returns the a list with all typed actors that has been put under supervision for the class specified.
*
* @param clazz the class for the active object
* @return a list with all the active objects for the class
* @param clazz the class for the typed actor
* @return a list with all the typed actors for the class
*/
def getInstances[T](clazz: Class[T]): JList[T] = INSTANCE.getInstance(clazz).foldLeft(new ArrayList[T]){ (l, i) => l add i ; l }
def getInstances[T](clazz: Class[T]): JList[T] =
INSTANCE.getInstance(clazz).foldLeft(new ArrayList[T]){ (l, i) => l add i ; l }
/**
* Returns the first item in a list of all active objects that has been put under supervision for the class specified.
* Returns the first item in a list of all typed actors that has been put under supervision for the class specified.
*
* @param clazz the class for the active object
* @return the active object for the class
* @param clazz the class for the typed actor
* @return the typed actor for the class
*/
def getInstance[T](clazz: Class[T]): T = INSTANCE.getInstance(clazz).head
def configure(restartStrategy: RestartStrategy, components: Array[Component]): ActiveObjectConfigurator = {
def configure(restartStrategy: RestartStrategy, components: Array[Component]): TypedActorConfigurator = {
INSTANCE.configure(
restartStrategy.transform,
components.toList.asInstanceOf[scala.List[Component]].map(_.transform))
this
}
def inject: ActiveObjectConfigurator = {
def inject: TypedActorConfigurator = {
INSTANCE.inject
this
}
def supervise: ActiveObjectConfigurator = {
def supervise: TypedActorConfigurator = {
INSTANCE.supervise
this
}
def addExternalGuiceModule(module: Module): ActiveObjectConfigurator = {
def addExternalGuiceModule(module: Module): TypedActorConfigurator = {
INSTANCE.addExternalGuiceModule(module)
this
}

View file

@ -7,7 +7,7 @@ package se.scalablesolutions.akka.config
import com.google.inject._
import se.scalablesolutions.akka.config.ScalaConfig._
import se.scalablesolutions.akka.actor.{Supervisor, ActiveObject, Dispatcher, ActorRef, Actor, IllegalActorStateException}
import se.scalablesolutions.akka.actor.{Supervisor, TypedActor, Dispatcher, ActorRef, Actor, IllegalActorStateException}
import se.scalablesolutions.akka.remote.RemoteServer
import se.scalablesolutions.akka.util.Logging
@ -17,12 +17,12 @@ import java.net.InetSocketAddress
import java.lang.reflect.Method
/**
* This is an class for internal usage. Instead use the <code>se.scalablesolutions.akka.config.ActiveObjectConfigurator</code>
* class for creating ActiveObjects.
* This is an class for internal usage. Instead use the <code>se.scalablesolutions.akka.config.TypedActorConfigurator</code>
* class for creating TypedActors.
*
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
private[akka] class ActiveObjectGuiceConfigurator extends ActiveObjectConfiguratorBase with Logging {
private[akka] class TypedActorGuiceConfigurator extends TypedActorConfiguratorBase with Logging {
private var injector: Injector = _
private var supervisor: Option[Supervisor] = None
private var restartStrategy: RestartStrategy = _
@ -30,22 +30,22 @@ private[akka] class ActiveObjectGuiceConfigurator extends ActiveObjectConfigurat
private var supervised: List[Supervise] = Nil
private var bindings: List[DependencyBinding] = Nil
private var configRegistry = new HashMap[Class[_], Component] // TODO is configRegistry needed?
private var activeObjectRegistry = new HashMap[Class[_], Tuple3[AnyRef, AnyRef, Component]]
private var typedActorRegistry = new HashMap[Class[_], Tuple3[AnyRef, AnyRef, Component]]
private var modules = new java.util.ArrayList[Module]
private var methodToUriRegistry = new HashMap[Method, String]
/**
* Returns the active abject that has been put under supervision for the class specified.
*
* @param clazz the class for the active object
* @return the active objects for the class
* @param clazz the class for the typed actor
* @return the typed actors for the class
*/
def getInstance[T](clazz: Class[T]): List[T] = synchronized {
log.debug("Retrieving active object [%s]", clazz.getName)
log.debug("Retrieving typed actor [%s]", clazz.getName)
if (injector eq null) throw new IllegalActorStateException(
"inject() and/or supervise() must be called before invoking getInstance(clazz)")
val (proxy, targetInstance, component) =
activeObjectRegistry.getOrElse(clazz, throw new IllegalActorStateException(
typedActorRegistry.getOrElse(clazz, throw new IllegalActorStateException(
"Class [" + clazz.getName + "] has not been put under supervision" +
"\n(by passing in the config to the 'configure' and then invoking 'supervise') method"))
injector.injectMembers(targetInstance)
@ -53,7 +53,7 @@ private[akka] class ActiveObjectGuiceConfigurator extends ActiveObjectConfigurat
}
def isDefined(clazz: Class[_]): Boolean = synchronized {
activeObjectRegistry.get(clazz).isDefined
typedActorRegistry.get(clazz).isDefined
}
override def getExternalDependency[T](clazz: Class[T]): T = synchronized {
@ -67,7 +67,7 @@ private[akka] class ActiveObjectGuiceConfigurator extends ActiveObjectConfigurat
}
override def configure(restartStrategy: RestartStrategy, components: List[Component]):
ActiveObjectConfiguratorBase = synchronized {
TypedActorConfiguratorBase = synchronized {
this.restartStrategy = restartStrategy
this.components = components.toArray.toList.asInstanceOf[List[Component]]
bindings = for (component <- this.components) yield {
@ -76,63 +76,72 @@ private[akka] class ActiveObjectGuiceConfigurator extends ActiveObjectConfigurat
}
val deps = new java.util.ArrayList[DependencyBinding](bindings.size)
for (b <- bindings) deps.add(b)
modules.add(new ActiveObjectGuiceModule(deps))
modules.add(new TypedActorGuiceModule(deps))
this
}
private def newSubclassingProxy(component: Component): DependencyBinding = {
val targetClass = component.target
val actorRef = Actor.actorOf(new Dispatcher(component.transactionRequired,
component.lifeCycle.restartCallbacks,
component.lifeCycle.shutdownCallback))
val targetClass =
if (component.target.isInstanceOf[Class[_ <: TypedActor]]) component.target.asInstanceOf[Class[_ <: TypedActor]]
else throw new IllegalArgumentException("TypedActor [" + component.target.getName + "] must be a subclass of TypedActor")
val actorRef = Actor.actorOf(new Dispatcher(component.transactionRequired))
if (component.dispatcher.isDefined) actorRef.dispatcher = component.dispatcher.get
val remoteAddress =
if (component.remoteAddress.isDefined)
Some(new InetSocketAddress(component.remoteAddress.get.hostname, component.remoteAddress.get.port))
else None
val proxy = ActiveObject.newInstance(targetClass, actorRef, remoteAddress, component.timeout).asInstanceOf[AnyRef]
remoteAddress.foreach(address => RemoteServer.registerActiveObject(address, targetClass.getName, proxy))
val proxy = TypedActor.newInstance(targetClass, actorRef, remoteAddress, component.timeout).asInstanceOf[AnyRef]
remoteAddress.foreach(address => RemoteServer.registerTypedActor(address, targetClass.getName, proxy))
supervised ::= Supervise(actorRef, component.lifeCycle)
activeObjectRegistry.put(targetClass, (proxy, proxy, component))
typedActorRegistry.put(targetClass, (proxy, proxy, component))
new DependencyBinding(targetClass, proxy)
}
private def newDelegatingProxy(component: Component): DependencyBinding = {
val targetClass = component.intf.get
val targetInstance = component.target.newInstance.asInstanceOf[AnyRef] // TODO: perhaps need to put in registry
component.target.getConstructor(Array[Class[_]](): _*).setAccessible(true)
val actorRef = Actor.actorOf(new Dispatcher(component.transactionRequired,
component.lifeCycle.restartCallbacks,
component.lifeCycle.shutdownCallback))
val targetClass = component.intf.get
val instance = component.target.newInstance.asInstanceOf[AnyRef] // TODO: perhaps need to put in registry
val targetInstance =
if (instance.isInstanceOf[TypedActor]) instance.asInstanceOf[TypedActor]
else throw new IllegalArgumentException("TypedActor [" + component.target.getName + "] must be a subclass of TypedActor")
val actorRef = Actor.actorOf(new Dispatcher(component.transactionRequired))
if (component.dispatcher.isDefined) actorRef.dispatcher = component.dispatcher.get
val remoteAddress =
if (component.remoteAddress.isDefined)
Some(new InetSocketAddress(component.remoteAddress.get.hostname, component.remoteAddress.get.port))
else None
val proxy = ActiveObject.newInstance(
val proxy = TypedActor.newInstance(
targetClass, targetInstance, actorRef, remoteAddress, component.timeout).asInstanceOf[AnyRef]
remoteAddress.foreach(address => RemoteServer.registerActiveObject(address, targetClass.getName, proxy))
remoteAddress.foreach(address => RemoteServer.registerTypedActor(address, targetClass.getName, proxy))
supervised ::= Supervise(actorRef, component.lifeCycle)
activeObjectRegistry.put(targetClass, (proxy, targetInstance, component))
typedActorRegistry.put(targetClass, (proxy, targetInstance, component))
new DependencyBinding(targetClass, proxy)
}
override def inject: ActiveObjectConfiguratorBase = synchronized {
override def inject: TypedActorConfiguratorBase = synchronized {
if (injector ne null) throw new IllegalActorStateException("inject() has already been called on this configurator")
injector = Guice.createInjector(modules)
this
}
override def supervise: ActiveObjectConfiguratorBase = synchronized {
override def supervise: TypedActorConfiguratorBase = synchronized {
if (injector eq null) inject
supervisor = Some(ActiveObject.supervise(restartStrategy, supervised))
supervisor = Some(TypedActor.supervise(restartStrategy, supervised))
this
}
/**
* Add additional services to be wired in.
* <pre>
* activeObjectConfigurator.addExternalGuiceModule(new AbstractModule {
* typedActorConfigurator.addExternalGuiceModule(new AbstractModule {
* protected void configure() {
* bind(Foo.class).to(FooImpl.class).in(Scopes.SINGLETON);
* bind(BarImpl.class);
@ -141,7 +150,7 @@ private[akka] class ActiveObjectGuiceConfigurator extends ActiveObjectConfigurat
* }})
* </pre>
*/
def addExternalGuiceModule(module: Module): ActiveObjectConfiguratorBase = synchronized {
def addExternalGuiceModule(module: Module): TypedActorConfiguratorBase = synchronized {
modules.add(module)
this
}
@ -151,7 +160,7 @@ private[akka] class ActiveObjectGuiceConfigurator extends ActiveObjectConfigurat
def reset = synchronized {
modules = new java.util.ArrayList[Module]
configRegistry = new HashMap[Class[_], Component]
activeObjectRegistry = new HashMap[Class[_], Tuple3[AnyRef, AnyRef, Component]]
typedActorRegistry = new HashMap[Class[_], Tuple3[AnyRef, AnyRef, Component]]
methodToUriRegistry = new HashMap[Method, String]
injector = null
restartStrategy = null

View file

@ -29,6 +29,7 @@ abstract class AbstractReactorBasedEventDrivenDispatcher(val name: String) exten
}
def shutdown = if (active) {
log.debug("Shutting down %s", toString)
active = false
selectorThread.interrupt
doShutdown

View file

@ -5,6 +5,7 @@
package se.scalablesolutions.akka.dispatch
import se.scalablesolutions.akka.actor.{ActorRef, IllegalActorStateException}
import jsr166x.ConcurrentLinkedDeque
/**
* Default settings are:
@ -64,18 +65,37 @@ class ExecutorBasedEventDrivenDispatcher(_name: String, throughput: Int = Dispat
@volatile private var active: Boolean = false
val name: String = "event-driven:executor:dispatcher:" + _name
val name = "akka:event-driven:dispatcher:" + _name
init
def dispatch(invocation: MessageInvocation) = dispatch(invocation.receiver)
def dispatch(invocation: MessageInvocation) = {
getMailbox(invocation.receiver).add(invocation)
dispatch(invocation.receiver)
}
/**
* @return the mailbox associated with the actor
*/
private def getMailbox(receiver: ActorRef) = receiver.mailbox.asInstanceOf[ConcurrentLinkedDeque[MessageInvocation]]
override def mailboxSize(actorRef: ActorRef) = getMailbox(actorRef).size
override def register(actorRef: ActorRef) = {
// The actor will need a ConcurrentLinkedDeque based mailbox
if( actorRef.mailbox == null ) {
actorRef.mailbox = new ConcurrentLinkedDeque[MessageInvocation]()
}
super.register(actorRef)
}
def dispatch(receiver: ActorRef): Unit = if (active) {
executor.execute(new Runnable() {
def run = {
var lockAcquiredOnce = false
var finishedBeforeMailboxEmpty = false
val lock = receiver.dispatcherLock
val mailbox = receiver.mailbox
val mailbox = getMailbox(receiver)
// this do-while loop is required to prevent missing new messages between the end of the inner while
// loop and releasing the lock
do {
@ -92,7 +112,9 @@ class ExecutorBasedEventDrivenDispatcher(_name: String, throughput: Int = Dispat
} while ((lockAcquiredOnce && !finishedBeforeMailboxEmpty && !mailbox.isEmpty))
}
})
} else throw new IllegalActorStateException("Can't submit invocations to dispatcher since it's not started")
} else {
log.warning("%s is shut down,\n\tignoring the rest of the messages in the mailbox of\n\t%s", toString, receiver)
}
/**
@ -102,39 +124,38 @@ class ExecutorBasedEventDrivenDispatcher(_name: String, throughput: Int = Dispat
*/
def processMailbox(receiver: ActorRef): Boolean = {
var processedMessages = 0
var messageInvocation = receiver.mailbox.poll
val mailbox = getMailbox(receiver)
var messageInvocation = mailbox.poll
while (messageInvocation != null) {
messageInvocation.invoke
processedMessages += 1
// check if we simply continue with other messages, or reached the throughput limit
if (throughput <= 0 || processedMessages < throughput)
messageInvocation = receiver.mailbox.poll
if (throughput <= 0 || processedMessages < throughput) messageInvocation = mailbox.poll
else {
return !receiver.mailbox.isEmpty
messageInvocation = null
return !mailbox.isEmpty
}
}
return false
false
}
def start = if (!active) {
log.debug("Starting ExecutorBasedEventDrivenDispatcher [%s]", name)
log.debug("Throughput for %s = %d", name, throughput)
log.debug("Starting up %s\n\twith throughput [%d]", toString, throughput)
active = true
}
def shutdown = if (active) {
log.debug("Shutting down ExecutorBasedEventDrivenDispatcher [%s]", name)
log.debug("Shutting down %s", toString)
executor.shutdownNow
active = false
references.clear
}
def usesActorMailbox = true
def ensureNotActive(): Unit = if (active) throw new IllegalActorStateException(
"Can't build a new thread pool for a dispatcher that is already up and running")
override def toString = "ExecutorBasedEventDrivenDispatcher[" + name + "]"
// FIXME: should we have an unbounded queue and not bounded as default ????
private[akka] def init = withNewThreadPoolWithLinkedBlockingQueueWithUnboundedCapacity.buildThreadPool
}

View file

@ -7,6 +7,7 @@ package se.scalablesolutions.akka.dispatch
import java.util.concurrent.CopyOnWriteArrayList
import se.scalablesolutions.akka.actor.{Actor, ActorRef, IllegalActorStateException}
import jsr166x.ConcurrentLinkedDeque
/**
* An executor based event driven dispatcher which will try to redistribute work from busy actors to idle actors. It is assumed
@ -41,11 +42,19 @@ class ExecutorBasedEventDrivenWorkStealingDispatcher(_name: String) extends Mess
/** The index in the pooled actors list which was last used to steal work */
@volatile private var lastThiefIndex = 0
// TODO: is there a naming convention for this name?
val name: String = "event-driven-work-stealing:executor:dispatcher:" + _name
val name = "akka:event-driven-work-stealing:dispatcher:" + _name
init
/**
* @return the mailbox associated with the actor
*/
private def getMailbox(receiver: ActorRef) = receiver.mailbox.asInstanceOf[ConcurrentLinkedDeque[MessageInvocation]]
override def mailboxSize(actorRef: ActorRef) = getMailbox(actorRef).size
def dispatch(invocation: MessageInvocation) = if (active) {
getMailbox(invocation.receiver).add(invocation)
executor.execute(new Runnable() {
def run = {
if (!tryProcessMailbox(invocation.receiver)) {
@ -77,7 +86,7 @@ class ExecutorBasedEventDrivenWorkStealingDispatcher(_name: String) extends Mess
lock.unlock
}
}
} while ((lockAcquiredOnce && !receiver.mailbox.isEmpty))
} while ((lockAcquiredOnce && !getMailbox(receiver).isEmpty))
return lockAcquiredOnce
}
@ -86,10 +95,11 @@ class ExecutorBasedEventDrivenWorkStealingDispatcher(_name: String) extends Mess
* Process the messages in the mailbox of the given actor.
*/
private def processMailbox(receiver: ActorRef) = {
var messageInvocation = receiver.mailbox.poll
val mailbox = getMailbox(receiver)
var messageInvocation = mailbox.poll
while (messageInvocation != null) {
messageInvocation.invoke
messageInvocation = receiver.mailbox.poll
messageInvocation = mailbox.poll
}
}
@ -117,7 +127,7 @@ class ExecutorBasedEventDrivenWorkStealingDispatcher(_name: String) extends Mess
for (i <- 0 to actors.length) {
val index = (i + startIndex) % actors.length
val actor = actors(index)
if (actor != receiver && actor.mailbox.isEmpty) return (Some(actor), index)
if (actor != receiver && getMailbox(actor).isEmpty) return (Some(actor), index)
}
(None, startIndex) // nothing found, reuse same start index next time
}
@ -129,8 +139,7 @@ class ExecutorBasedEventDrivenWorkStealingDispatcher(_name: String) extends Mess
private def tryDonateAndProcessMessages(receiver: ActorRef, thief: ActorRef) = {
if (thief.dispatcherLock.tryLock) {
try {
while(donateMessage(receiver, thief))
processMailbox(thief)
while(donateMessage(receiver, thief)) processMailbox(thief)
} finally {
thief.dispatcherLock.unlock
}
@ -141,7 +150,7 @@ class ExecutorBasedEventDrivenWorkStealingDispatcher(_name: String) extends Mess
* Steal a message from the receiver and give it to the thief.
*/
private def donateMessage(receiver: ActorRef, thief: ActorRef): Boolean = {
val donated = receiver.mailbox.pollLast
val donated = getMailbox(receiver).pollLast
if (donated ne null) {
if (donated.senderFuture.isDefined) thief.self.postMessageToMailboxAndCreateFutureResultWithTimeout[Any](
donated.message, receiver.timeout, donated.sender, donated.senderFuture)
@ -156,7 +165,7 @@ class ExecutorBasedEventDrivenWorkStealingDispatcher(_name: String) extends Mess
}
def shutdown = if (active) {
log.debug("Shutting down ExecutorBasedEventDrivenWorkStealingDispatcher [%s]", name)
log.debug("Shutting down %s", toString)
executor.shutdownNow
active = false
references.clear
@ -165,10 +174,16 @@ class ExecutorBasedEventDrivenWorkStealingDispatcher(_name: String) extends Mess
def ensureNotActive(): Unit = if (active) throw new IllegalActorStateException(
"Can't build a new thread pool for a dispatcher that is already up and running")
override def toString = "ExecutorBasedEventDrivenWorkStealingDispatcher[" + name + "]"
private[akka] def init = withNewThreadPoolWithLinkedBlockingQueueWithUnboundedCapacity.buildThreadPool
override def register(actorRef: ActorRef) = {
verifyActorsAreOfSameType(actorRef)
// The actor will need a ConcurrentLinkedDeque based mailbox
if( actorRef.mailbox == null ) {
actorRef.mailbox = new ConcurrentLinkedDeque[MessageInvocation]()
}
pooledActors.add(actorRef)
super.register(actorRef)
}
@ -178,19 +193,14 @@ class ExecutorBasedEventDrivenWorkStealingDispatcher(_name: String) extends Mess
super.unregister(actorRef)
}
def usesActorMailbox = true
private def verifyActorsAreOfSameType(actorOfId: ActorRef) = {
actorType match {
case None => {
actorType = Some(actorOfId.actor.getClass)
}
case Some(aType) => {
case None => actorType = Some(actorOfId.actor.getClass)
case Some(aType) =>
if (aType != actorOfId.actor.getClass)
throw new IllegalActorStateException(
String.format("Can't register actor %s in a work stealing dispatcher which already knows actors of type %s",
actorOfId.actor, aType))
}
throw new IllegalActorStateException(String.format(
"Can't register actor %s in a work stealing dispatcher which already knows actors of type %s",
actorOfId.actor, aType))
}
}
}

View file

@ -0,0 +1,249 @@
/**
* Copyright (C) 2010, Progress Software Corporation and/or its
* subsidiaries or affiliates. All rights reserved.
*
* 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.dispatch
import se.scalablesolutions.akka.actor.ActorRef
import org.fusesource.hawtdispatch.DispatchQueue
import org.fusesource.hawtdispatch.ScalaDispatch._
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.CountDownLatch
import org.fusesource.hawtdispatch.DispatchQueue.QueueType
import org.fusesource.hawtdispatch.ListEventAggregator
/**
* Holds helper methods for working with actors that are using
* a HawtDispatcher as it's dispatcher.
*/
object HawtDispatcher {
private val retained = new AtomicInteger()
@volatile private var shutdownLatch: CountDownLatch = _
private def retainNonDaemon = {
if( retained.getAndIncrement == 0 ) {
shutdownLatch = new CountDownLatch(1)
new Thread("HawtDispatch Non-Daemon") {
override def run = {
try {
shutdownLatch.await
} catch {
case _ =>
}
println("done");
}
}.start()
}
}
private def releaseNonDaemon = {
if( retained.decrementAndGet == 0 ) {
shutdownLatch.countDown
shutdownLatch = null
}
}
/**
* @return the mailbox associated with the actor
*/
private def mailbox(actorRef: ActorRef) = {
actorRef.mailbox.asInstanceOf[HawtDispatcherMailbox]
}
/**
* @return the dispatch queue associated with the actor
*/
def queue(actorRef: ActorRef) = {
mailbox(actorRef).queue
}
/**
* <p>
* Pins an actor to a random thread queue. Once pinned the actor will always execute
* on the same thread.
* </p>
*
* <p>
* This method can only succeed if the actor it's dispatcher is set to a HawtDispatcher and it has been started
* </p>
*
* @return true if the actor was pinned
*/
def pin(actorRef: ActorRef) = {
actorRef.mailbox match {
case x:HawtDispatcherMailbox=>
x.queue.setTargetQueue( getRandomThreadQueue )
true
case _ => false
}
}
/**
* <p>
* Unpins the actor so that all threads in the hawt dispatch thread pool
* compete to execute him.
* </p>
*
* <p>
* This method can only succeed if the actor it's dispatcher is set to a HawtDispatcher and it has been started
* </p>
* @return true if the actor was unpinned
*/
def unpin(actorRef: ActorRef) = {
target(actorRef, globalQueue)
}
/**
* @return true if the actor was pinned to a thread.
*/
def pinned(actorRef: ActorRef):Boolean = {
actorRef.mailbox match {
case x:HawtDispatcherMailbox=>
x.queue.getTargetQueue.getQueueType == QueueType.THREAD_QUEUE
case _ => false
}
}
/**
* <p>
* Updates the actor's target dispatch queue to the value specified. This allows
* you to do odd things like targeting another serial queue.
* </p>
*
* <p>
* This method can only succeed if the actor it's dispatcher is set to a HawtDispatcher and it has been started
* </p>
* @return true if the actor was unpinned
*/
def target(actorRef: ActorRef, parent:DispatchQueue) = {
actorRef.mailbox match {
case x:HawtDispatcherMailbox=>
x.queue.setTargetQueue( parent )
true
case _ => false
}
}
}
/**
* <p>
* A HawtDispatch based MessageDispatcher. Actors with this dispatcher are executed
* on the HawtDispatch fixed sized thread pool. The number of of threads will match
* the number of cores available on your system.
*
* </p>
* <p>
* Actors using this dispatcher are restricted to only executing non blocking
* operations. The actor cannot synchronously call another actor or call 3rd party
* libraries that can block for a long time. You should use non blocking IO APIs
* instead of blocking IO apis to avoid blocking that actor for an extended amount
* of time.
* </p>
*
* <p>
* This dispatcher delivers messages to the actors in the order that they
* were producer at the sender.
* </p>
*
* <p>
* HawtDispatch supports processing Non blocking Socket IO in both the reactor
* and proactor styles. For more details, see the <code>HawtDispacherEchoServer.scala</code>
* example.
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
class HawtDispatcher(val aggregate:Boolean=true, val parent:DispatchQueue=globalQueue) extends MessageDispatcher {
import HawtDispatcher._
private val active = new AtomicBoolean(false)
def start = {
if( active.compareAndSet(false, true) ) {
retainNonDaemon
}
}
def shutdown = {
if( active.compareAndSet(true, false) ) {
releaseNonDaemon
}
}
def isShutdown = !active.get
def dispatch(invocation: MessageInvocation) = if(active.get()) {
mailbox(invocation.receiver).dispatch(invocation)
} else {
log.warning("%s is shut down,\n\tignoring the the messages sent to\n\t%s", toString, invocation.receiver)
}
// hawtdispatch does not have a way to get queue sizes, getting an accurate
// size can cause extra contention.. is this really needed?
// TODO: figure out if this can be optional in akka
override def mailboxSize(actorRef: ActorRef) = 0
override def register(actorRef: ActorRef) = {
if( actorRef.mailbox == null ) {
val queue = parent.createSerialQueue(actorRef.toString)
if( aggregate ) {
actorRef.mailbox = new AggregatingHawtDispatcherMailbox(queue)
} else {
actorRef.mailbox = new HawtDispatcherMailbox(queue)
}
}
super.register(actorRef)
}
override def toString = "HawtDispatchEventDrivenDispatcher"
}
class HawtDispatcherMailbox(val queue:DispatchQueue) {
def dispatch(invocation: MessageInvocation):Unit = {
queue {
invocation.invoke
}
}
}
class AggregatingHawtDispatcherMailbox(queue:DispatchQueue) extends HawtDispatcherMailbox(queue) {
private val source = createSource(new ListEventAggregator[MessageInvocation](), queue)
source.setEventHandler (^{drain_source} )
source.resume
private def drain_source = {
source.getData.foreach { invocation =>
invocation.invoke
}
}
override def dispatch(invocation: MessageInvocation):Unit = {
if ( getCurrentQueue == null ) {
// we are being call from a non hawtdispatch thread, can't aggregate
// it's events
super.dispatch(invocation)
} else {
// we are being call from a hawtdispatch thread, use the dispatch source
// so that multiple invocations issues on this thread will aggregate and then once
// the thread runs out of work, they get transferred as a batch to the other thread.
source.merge(invocation)
}
}
}

View file

@ -53,7 +53,7 @@ final class MessageInvocation(val receiver: ActorRef,
"\n\tsender = " + sender +
"\n\tsenderFuture = " + senderFuture +
"\n\ttransactionSet = " + transactionSet +
"\n]"
"]"
}
}
@ -79,7 +79,7 @@ trait MessageDispatcher extends Logging {
}
def canBeShutDown: Boolean = references.isEmpty
def isShutdown: Boolean
def usesActorMailbox : Boolean
def mailboxSize(actorRef: ActorRef):Int = 0
}
/**

View file

@ -12,11 +12,14 @@ package se.scalablesolutions.akka.dispatch
import java.util.{LinkedList, List}
class ReactorBasedSingleThreadEventDrivenDispatcher(name: String) extends AbstractReactorBasedEventDrivenDispatcher(name) {
class ReactorBasedSingleThreadEventDrivenDispatcher(_name: String)
extends AbstractReactorBasedEventDrivenDispatcher("akka:event-driven:reactor:single-thread:dispatcher:" + _name) {
def start = if (!active) {
log.debug("Starting up %s", toString)
active = true
val messageDemultiplexer = new Demultiplexer(queue)
selectorThread = new Thread("event-driven:reactor:single-thread:dispatcher:" + name) {
selectorThread = new Thread(name) {
override def run = {
while (active) {
try {
@ -38,7 +41,7 @@ class ReactorBasedSingleThreadEventDrivenDispatcher(name: String) extends Abstra
def isShutdown = !active
def usesActorMailbox = false
override def toString = "ReactorBasedSingleThreadEventDrivenDispatcher[" + name + "]"
class Demultiplexer(private val messageQueue: ReactiveMessageQueue) extends MessageDemultiplexer {

View file

@ -64,7 +64,7 @@ import se.scalablesolutions.akka.actor.IllegalActorStateException
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
class ReactorBasedThreadPoolEventDrivenDispatcher(_name: String)
extends AbstractReactorBasedEventDrivenDispatcher("event-driven:reactor:thread-pool:dispatcher:" + _name)
extends AbstractReactorBasedEventDrivenDispatcher("akka:event-driven:reactor:dispatcher:" + _name)
with ThreadPoolBuilder {
private var fair = true
@ -75,17 +75,18 @@ class ReactorBasedThreadPoolEventDrivenDispatcher(_name: String)
withNewThreadPoolWithLinkedBlockingQueueWithUnboundedCapacity.buildThreadPool
def start = if (!active) {
log.debug("Starting up %s", toString)
active = true
/**
* This dispatcher code is based on code from the actorom actor framework by Sergio Bossa [http://code.google.com/p/actorom/].
* This dispatcher code is based on code from the actorom actor framework by Sergio Bossa
* [http://code.google.com/p/actorom/].
*/
selectorThread = new Thread(name) {
override def run = {
while (active) {
try {
try {
// guard.synchronized { /* empty */ } // prevents risk for deadlock as described in [http://developers.sun.com/learning/javaoneonline/2006/coreplatform/TS-1315.pdf]
messageDemultiplexer.select
} catch { case e: InterruptedException => active = false }
process(messageDemultiplexer.acquireSelectedInvocations)
@ -110,7 +111,8 @@ class ReactorBasedThreadPoolEventDrivenDispatcher(_name: String)
if (invocation eq null) throw new IllegalActorStateException("Message invocation is null [" + invocation + "]")
if (!busyActors.contains(invocation.receiver)) {
val invoker = messageInvokers.get(invocation.receiver)
if (invoker eq null) throw new IllegalActorStateException("Message invoker for invocation [" + invocation + "] is null")
if (invoker eq null) throw new IllegalActorStateException(
"Message invoker for invocation [" + invocation + "] is null")
resume(invocation.receiver)
invocations.remove
executor.execute(new Runnable() {
@ -137,11 +139,11 @@ class ReactorBasedThreadPoolEventDrivenDispatcher(_name: String)
else nrOfBusyMessages < 100
}
def usesActorMailbox = false
def ensureNotActive(): Unit = if (active) throw new IllegalActorStateException(
"Can't build a new thread pool for a dispatcher that is already up and running")
override def toString = "ReactorBasedThreadPoolEventDrivenDispatcher[" + name + "]"
class Demultiplexer(private val messageQueue: ReactiveMessageQueue) extends MessageDemultiplexer {
private val selectedInvocations: List[MessageInvocation] = new LinkedList[MessageInvocation]
private val selectedInvocationsLock = new ReentrantLock

View file

@ -16,7 +16,7 @@ import se.scalablesolutions.akka.actor.{Actor, ActorRef}
*/
class ThreadBasedDispatcher(private val actor: ActorRef) extends MessageDispatcher {
private val name = actor.getClass.getName + ":" + actor.uuid
private val threadName = "thread-based:dispatcher:" + name
private val threadName = "akka:thread-based:dispatcher:" + name
private val queue = new BlockingMessageQueue(name)
private var selectorThread: Thread = _
@volatile private var active: Boolean = false
@ -24,6 +24,7 @@ class ThreadBasedDispatcher(private val actor: ActorRef) extends MessageDispatch
def dispatch(invocation: MessageInvocation) = queue.append(invocation)
def start = if (!active) {
log.debug("Starting up %s", toString)
active = true
selectorThread = new Thread(threadName) {
override def run = {
@ -39,14 +40,14 @@ class ThreadBasedDispatcher(private val actor: ActorRef) extends MessageDispatch
def isShutdown = !active
def usesActorMailbox = false
def shutdown = if (active) {
log.debug("Shutting down ThreadBasedDispatcher [%s]", name)
log.debug("Shutting down %s", toString)
active = false
selectorThread.interrupt
references.clear
}
override def toString = "ThreadBasedDispatcher[" + threadName + "]"
}
class BlockingMessageQueue(name: String) extends MessageQueue {

View file

@ -234,18 +234,19 @@ trait ThreadPoolBuilder {
extends Thread(runnable, name + "-" + MonitorableThread.created.incrementAndGet) with Logging {
setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
def uncaughtException(thread: Thread, cause: Throwable) = log.error(cause, "UNCAUGHT in thread [%s]", thread.getName)
def uncaughtException(thread: Thread, cause: Throwable) =
log.error(cause, "UNCAUGHT in thread [%s]", thread.getName)
})
override def run = {
val debug = MonitorableThread.debugLifecycle
log.debug("Created %s", getName)
log.debug("Created thread %s", getName)
try {
MonitorableThread.alive.incrementAndGet
super.run
} finally {
MonitorableThread.alive.decrementAndGet
log.debug("Exiting %s", getName)
log.debug("Exiting thread %s", getName)
}
}
}

View file

@ -151,11 +151,17 @@ abstract class BasicClusterActor extends ClusterActor with Logging {
case InitClusterActor(s) => {
serializer = s
boot
}
}
/**
* Implement this in a subclass to add node-to-node messaging
* Implement this in a subclass to boot up the cluster implementation
*/
protected def boot: Unit
/**
* Implement this in a subclass to add node-to-node messaging
*/
protected def toOneNode(dest: ADDR_T, msg: Array[Byte]): Unit

View file

@ -17,9 +17,8 @@ class JGroupsClusterActor extends BasicClusterActor {
@volatile private var isActive = false
@volatile private var channel: Option[JChannel] = None
override def init = {
super.init
log info "Initiating JGroups-based cluster actor"
protected def boot = {
log info "Booting JGroups-based cluster"
isActive = true
// Set up the JGroups local endpoint

View file

@ -7,8 +7,8 @@ package se.scalablesolutions.akka.remote
import se.scalablesolutions.akka.remote.protocol.RemoteProtocol._
import se.scalablesolutions.akka.actor.{Exit, Actor, ActorRef, RemoteActorRef, IllegalActorStateException}
import se.scalablesolutions.akka.dispatch.{DefaultCompletableFuture, CompletableFuture}
import se.scalablesolutions.akka.util.{UUID, Logging}
import se.scalablesolutions.akka.config.Config.config
import se.scalablesolutions.akka.util.{UUID, Logging, Duration}
import se.scalablesolutions.akka.config.Config._
import org.jboss.netty.channel._
import group.DefaultChannelGroup
@ -51,8 +51,8 @@ case class RemoteClientConnected(host: String, port: Int) extends RemoteClientLi
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
object RemoteClient extends Logging {
val READ_TIMEOUT = config.getInt("akka.remote.client.read-timeout", 10000)
val RECONNECT_DELAY = config.getInt("akka.remote.client.reconnect-delay", 5000)
val READ_TIMEOUT = Duration(config.getInt("akka.remote.client.read-timeout", 1), TIME_UNIT)
val RECONNECT_DELAY = Duration(config.getInt("akka.remote.client.reconnect-delay", 5), TIME_UNIT)
private val remoteClients = new HashMap[String, RemoteClient]
private val remoteActors = new HashMap[RemoteServer.Address, HashSet[String]]
@ -138,7 +138,7 @@ object RemoteClient extends Logging {
actorsFor(RemoteServer.Address(hostname, port)) += uuid
}
// TODO: add RemoteClient.unregister for ActiveObject, but first need a @shutdown callback
// TODO: add RemoteClient.unregister for TypedActor, but first need a @shutdown callback
private[akka] def unregister(hostname: String, port: Int, uuid: String) = synchronized {
val set = actorsFor(RemoteServer.Address(hostname, port))
set -= uuid
@ -218,7 +218,7 @@ class RemoteClient private[akka] (val hostname: String, val port: Int, loader: O
} else {
futures.synchronized {
val futureResult = if (senderFuture.isDefined) senderFuture.get
else new DefaultCompletableFuture[T](request.getTimeout)
else new DefaultCompletableFuture[T](request.getActorInfo.getTimeout)
futures.put(request.getId, futureResult)
connection.getChannel.write(request)
Some(futureResult)
@ -231,11 +231,13 @@ class RemoteClient private[akka] (val hostname: String, val port: Int, loader: O
}
private[akka] def registerSupervisorForActor(actorRef: ActorRef) =
if (!actorRef.supervisor.isDefined) throw new IllegalActorStateException("Can't register supervisor for " + actorRef + " since it is not under supervision")
if (!actorRef.supervisor.isDefined) throw new IllegalActorStateException(
"Can't register supervisor for " + actorRef + " since it is not under supervision")
else supervisors.putIfAbsent(actorRef.supervisor.get.uuid, actorRef)
private[akka] def deregisterSupervisorForActor(actorRef: ActorRef) =
if (!actorRef.supervisor.isDefined) throw new IllegalActorStateException("Can't unregister supervisor for " + actorRef + " since it is not under supervision")
if (!actorRef.supervisor.isDefined) throw new IllegalActorStateException(
"Can't unregister supervisor for " + actorRef + " since it is not under supervision")
else supervisors.remove(actorRef.supervisor.get.uuid)
}
@ -250,6 +252,7 @@ class RemoteClientPipelineFactory(name: String,
timer: HashedWheelTimer,
client: RemoteClient) extends ChannelPipelineFactory {
def getPipeline: ChannelPipeline = {
def join(ch: ChannelHandler*) = Array[ChannelHandler](ch:_*)
val engine = RemoteServerSslContext.client.createSSLEngine()
@ -257,7 +260,7 @@ class RemoteClientPipelineFactory(name: String,
engine.setUseClientMode(true)
val ssl = if(RemoteServer.SECURE) join(new SslHandler(engine)) else join()
val timeout = new ReadTimeoutHandler(timer, RemoteClient.READ_TIMEOUT)
val timeout = new ReadTimeoutHandler(timer, RemoteClient.READ_TIMEOUT.toMillis.toInt)
val lenDec = new LengthFieldBasedFrameDecoder(1048576, 0, 4, 0, 4)
val lenPrep = new LengthFieldPrepender(4)
val protobufDec = new ProtobufDecoder(RemoteReplyProtocol.getDefaultInstance)
@ -345,7 +348,7 @@ class RemoteClientHandler(val name: String,
log.error(client.connection.getCause, "Reconnection to [%s] has failed", remoteAddress)
}
}
}, RemoteClient.RECONNECT_DELAY, TimeUnit.MILLISECONDS)
}, RemoteClient.RECONNECT_DELAY.toMillis, TimeUnit.MILLISECONDS)
}
override def channelConnected(ctx: ChannelHandlerContext, event: ChannelStateEvent) = {

View file

@ -13,7 +13,7 @@ import se.scalablesolutions.akka.actor._
import se.scalablesolutions.akka.actor.Actor._
import se.scalablesolutions.akka.util._
import se.scalablesolutions.akka.remote.protocol.RemoteProtocol._
import se.scalablesolutions.akka.config.Config.config
import se.scalablesolutions.akka.config.Config._
import org.jboss.netty.bootstrap.ServerBootstrap
import org.jboss.netty.channel._
@ -57,7 +57,7 @@ object RemoteNode extends RemoteServer
/**
* For internal use only.
* Holds configuration variables, remote actors, remote active objects and remote servers.
* Holds configuration variables, remote actors, remote typed actors and remote servers.
*
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
@ -65,7 +65,7 @@ object RemoteServer {
val HOSTNAME = config.getString("akka.remote.server.hostname", "localhost")
val PORT = config.getInt("akka.remote.server.port", 9999)
val CONNECTION_TIMEOUT_MILLIS = config.getInt("akka.remote.server.connection-timeout", 1000)
val CONNECTION_TIMEOUT_MILLIS = Duration(config.getInt("akka.remote.server.connection-timeout", 1), TIME_UNIT)
val COMPRESSION_SCHEME = config.getString("akka.remote.compression-scheme", "zlib")
val ZLIB_COMPRESSION_LEVEL = {
@ -76,7 +76,8 @@ object RemoteServer {
}
val SECURE = {
if(config.getBool("akka.remote.ssl.service",false)){
//TODO: Remove this when SSL is in working condition
/*if(config.getBool("akka.remote.ssl.service",false)){
val properties = List(
("key-store-type" ,"keyStoreType"),
@ -97,7 +98,7 @@ object RemoteServer {
true
}
else
else */
false
}
@ -121,7 +122,7 @@ object RemoteServer {
private class RemoteActorSet {
private[RemoteServer] val actors = new ConcurrentHashMap[String, ActorRef]
private[RemoteServer] val activeObjects = new ConcurrentHashMap[String, AnyRef]
private[RemoteServer] val typedActors = new ConcurrentHashMap[String, AnyRef]
}
private val guard = new ReadWriteGuard
@ -132,8 +133,8 @@ object RemoteServer {
actorsFor(RemoteServer.Address(address.getHostName, address.getPort)).actors.put(uuid, actor)
}
private[akka] def registerActiveObject(address: InetSocketAddress, name: String, activeObject: AnyRef) = guard.withWriteGuard {
actorsFor(RemoteServer.Address(address.getHostName, address.getPort)).activeObjects.put(name, activeObject)
private[akka] def registerTypedActor(address: InetSocketAddress, name: String, typedActor: AnyRef) = guard.withWriteGuard {
actorsFor(RemoteServer.Address(address.getHostName, address.getPort)).typedActors.put(name, typedActor)
}
private[akka] def getOrCreateServer(address: InetSocketAddress): RemoteServer = guard.withWriteGuard {
@ -225,12 +226,12 @@ class RemoteServer extends Logging {
RemoteServer.register(hostname, port, this)
val remoteActorSet = RemoteServer.actorsFor(RemoteServer.Address(hostname, port))
val pipelineFactory = new RemoteServerPipelineFactory(
name, openChannels, loader, remoteActorSet.actors, remoteActorSet.activeObjects)
name, openChannels, loader, remoteActorSet.actors, remoteActorSet.typedActors)
bootstrap.setPipelineFactory(pipelineFactory)
bootstrap.setOption("child.tcpNoDelay", true)
bootstrap.setOption("child.keepAlive", true)
bootstrap.setOption("child.reuseAddress", true)
bootstrap.setOption("child.connectTimeoutMillis", RemoteServer.CONNECTION_TIMEOUT_MILLIS)
bootstrap.setOption("child.connectTimeoutMillis", RemoteServer.CONNECTION_TIMEOUT_MILLIS.toMillis)
openChannels.add(bootstrap.bind(new InetSocketAddress(hostname, port)))
_isRunning = true
Cluster.registerLocalNode(hostname, port)
@ -243,15 +244,20 @@ class RemoteServer extends Logging {
def shutdown = synchronized {
if (_isRunning) {
RemoteServer.unregister(hostname, port)
openChannels.disconnect
openChannels.close.awaitUninterruptibly
bootstrap.releaseExternalResources
Cluster.deregisterLocalNode(hostname, port)
try {
RemoteServer.unregister(hostname, port)
openChannels.disconnect
openChannels.close.awaitUninterruptibly
bootstrap.releaseExternalResources
Cluster.deregisterLocalNode(hostname, port)
} catch {
case e: java.nio.channels.ClosedChannelException => {}
case e => log.warning("Could not close remote server channel in a graceful way")
}
}
}
// TODO: register active object in RemoteServer as well
// TODO: register typed actor in RemoteServer as well
/**
* Register Remote Actor by the Actor's 'id' field. It starts the Actor if it is not started already.
@ -331,7 +337,7 @@ class RemoteServerPipelineFactory(
val openChannels: ChannelGroup,
val loader: Option[ClassLoader],
val actors: JMap[String, ActorRef],
val activeObjects: JMap[String, AnyRef]) extends ChannelPipelineFactory {
val typedActors: JMap[String, AnyRef]) extends ChannelPipelineFactory {
import RemoteServer._
def getPipeline: ChannelPipeline = {
@ -351,7 +357,7 @@ class RemoteServerPipelineFactory(
case _ => (join(),join())
}
val remoteServer = new RemoteServerHandler(name, openChannels, loader, actors, activeObjects)
val remoteServer = new RemoteServerHandler(name, openChannels, loader, actors, typedActors)
val stages = ssl ++ dec ++ join(lenDec, protobufDec) ++ enc ++ join(lenPrep, protobufEnc, remoteServer)
@ -368,7 +374,7 @@ class RemoteServerHandler(
val openChannels: ChannelGroup,
val applicationLoader: Option[ClassLoader],
val actors: JMap[String, ActorRef],
val activeObjects: JMap[String, AnyRef]) extends SimpleChannelUpstreamHandler with Logging {
val typedActors: JMap[String, AnyRef]) extends SimpleChannelUpstreamHandler with Logging {
val AW_PROXY_PREFIX = "$$ProxiedByAW".intern
applicationLoader.foreach(MessageSerializer.setClassLoader(_))
@ -422,139 +428,88 @@ class RemoteServerHandler(
private def handleRemoteRequestProtocol(request: RemoteRequestProtocol, channel: Channel) = {
log.debug("Received RemoteRequestProtocol[\n%s]", request.toString)
if (request.getIsActor) dispatchToActor(request, channel)
else dispatchToActiveObject(request, channel)
val actorType = request.getActorInfo.getActorType
if (actorType == ActorType.SCALA_ACTOR) dispatchToActor(request, channel)
else if (actorType == ActorType.JAVA_ACTOR) throw new IllegalActorStateException("ActorType JAVA_ACTOR is currently not supported")
else if (actorType == ActorType.TYPED_ACTOR) dispatchToTypedActor(request, channel)
else throw new IllegalActorStateException("Unknown ActorType [" + actorType + "]")
}
private def dispatchToActor(request: RemoteRequestProtocol, channel: Channel) = {
log.debug("Dispatching to remote actor [%s:%s]", request.getTarget, request.getUuid)
val actorRef = createActor(request.getTarget, request.getUuid, request.getTimeout)
val actorInfo = request.getActorInfo
log.debug("Dispatching to remote actor [%s:%s]", actorInfo.getTarget, actorInfo.getUuid)
val actorRef = createActor(actorInfo)
actorRef.start
val message = MessageSerializer.deserialize(request.getMessage)
val sender =
if (request.hasSender) Some(RemoteActorSerialization.fromProtobufToRemoteActorRef(request.getSender, applicationLoader))
else None
if (request.getIsOneWay) actorRef.!(message)(sender)
else {
try {
val resultOrNone = (actorRef.!!(message)(sender)).as[AnyRef]
val result = if (resultOrNone.isDefined) resultOrNone.get else null
log.debug("Returning result from actor invocation [%s]", result)
val replyBuilder = RemoteReplyProtocol.newBuilder
.setId(request.getId)
.setMessage(MessageSerializer.serialize(result))
.setIsSuccessful(true)
.setIsActor(true)
if (request.hasSupervisorUuid) replyBuilder.setSupervisorUuid(request.getSupervisorUuid)
val replyMessage = replyBuilder.build
channel.write(replyMessage)
channel.write(replyBuilder.build)
} catch {
case e: Throwable =>
log.error(e, "Could not invoke remote actor [%s]", request.getTarget)
val replyBuilder = RemoteReplyProtocol.newBuilder
.setId(request.getId)
.setException(ExceptionProtocol.newBuilder.setClassname(e.getClass.getName).setMessage(e.getMessage).build)
.setIsSuccessful(false)
.setIsActor(true)
if (request.hasSupervisorUuid) replyBuilder.setSupervisorUuid(request.getSupervisorUuid)
val replyMessage = replyBuilder.build
channel.write(replyMessage)
case e: Throwable => channel.write(createErrorReplyMessage(e, request, true))
}
}
}
private def dispatchToActiveObject(request: RemoteRequestProtocol, channel: Channel) = {
log.debug("Dispatching to remote active object [%s :: %s]", request.getMethod, request.getTarget)
val activeObject = createActiveObject(request.getTarget, request.getTimeout)
private def dispatchToTypedActor(request: RemoteRequestProtocol, channel: Channel) = {
val actorInfo = request.getActorInfo
val typedActorInfo = actorInfo.getTypedActorInfo
log.debug("Dispatching to remote typed actor [%s :: %s]", typedActorInfo.getMethod, typedActorInfo.getInterface)
val typedActor = createTypedActor(actorInfo)
val args = MessageSerializer.deserialize(request.getMessage).asInstanceOf[Array[AnyRef]].toList
val argClasses = args.map(_.getClass)
val (unescapedArgs, unescapedArgClasses) = unescapeArgs(args, argClasses, request.getTimeout)
try {
val messageReceiver = activeObject.getClass.getDeclaredMethod(
request.getMethod, unescapedArgClasses: _*)
if (request.getIsOneWay) messageReceiver.invoke(activeObject, unescapedArgs: _*)
val messageReceiver = typedActor.getClass.getDeclaredMethod(typedActorInfo.getMethod, argClasses: _*)
if (request.getIsOneWay) messageReceiver.invoke(typedActor, args: _*)
else {
val result = messageReceiver.invoke(activeObject, unescapedArgs: _*)
log.debug("Returning result from remote active object invocation [%s]", result)
val result = messageReceiver.invoke(typedActor, args: _*)
log.debug("Returning result from remote typed actor invocation [%s]", result)
val replyBuilder = RemoteReplyProtocol.newBuilder
.setId(request.getId)
.setMessage(MessageSerializer.serialize(result))
.setIsSuccessful(true)
.setIsActor(false)
if (request.hasSupervisorUuid) replyBuilder.setSupervisorUuid(request.getSupervisorUuid)
val replyMessage = replyBuilder.build
channel.write(replyMessage)
channel.write(replyBuilder.build)
}
} catch {
case e: InvocationTargetException =>
log.error(e.getCause, "Could not invoke remote active object [%s :: %s]", request.getMethod, request.getTarget)
val replyBuilder = RemoteReplyProtocol.newBuilder
.setId(request.getId)
.setException(ExceptionProtocol.newBuilder.setClassname(e.getCause.getClass.getName).setMessage(e.getCause.getMessage).build)
.setIsSuccessful(false)
.setIsActor(false)
if (request.hasSupervisorUuid) replyBuilder.setSupervisorUuid(request.getSupervisorUuid)
val replyMessage = replyBuilder.build
channel.write(replyMessage)
case e: Throwable =>
log.error(e, "Could not invoke remote active object [%s :: %s]", request.getMethod, request.getTarget)
val replyBuilder = RemoteReplyProtocol.newBuilder
.setId(request.getId)
.setException(ExceptionProtocol.newBuilder.setClassname(e.getClass.getName).setMessage(e.getMessage).build)
.setIsSuccessful(false)
.setIsActor(false)
if (request.hasSupervisorUuid) replyBuilder.setSupervisorUuid(request.getSupervisorUuid)
val replyMessage = replyBuilder.build
channel.write(replyMessage)
case e: InvocationTargetException => channel.write(createErrorReplyMessage(e.getCause, request, false))
case e: Throwable => channel.write(createErrorReplyMessage(e, request, false))
}
}
private def unescapeArgs(args: scala.List[AnyRef], argClasses: scala.List[Class[_]], timeout: Long) = {
val unescapedArgs = new Array[AnyRef](args.size)
val unescapedArgClasses = new Array[Class[_]](args.size)
val escapedArgs = for (i <- 0 until args.size) {
val arg = args(i)
if (arg.isInstanceOf[String] && arg.asInstanceOf[String].startsWith(AW_PROXY_PREFIX)) {
val argString = arg.asInstanceOf[String]
val proxyName = argString.replace(AW_PROXY_PREFIX, "")
val activeObject = createActiveObject(proxyName, timeout)
unescapedArgs(i) = activeObject
unescapedArgClasses(i) = Class.forName(proxyName)
} else {
unescapedArgs(i) = args(i)
unescapedArgClasses(i) = argClasses(i)
}
}
(unescapedArgs, unescapedArgClasses)
}
private def createActiveObject(name: String, timeout: Long): AnyRef = {
val activeObjectOrNull = activeObjects.get(name)
if (activeObjectOrNull eq null) {
try {
log.info("Creating a new remote active object [%s]", name)
val clazz = if (applicationLoader.isDefined) applicationLoader.get.loadClass(name)
else Class.forName(name)
val newInstance = ActiveObject.newInstance(clazz, timeout).asInstanceOf[AnyRef]
activeObjects.put(name, newInstance)
newInstance
} catch {
case e =>
log.error(e, "Could not create remote active object instance")
throw e
}
} else activeObjectOrNull
}
/**
* Creates a new instance of the actor with name, uuid and timeout specified as arguments.
*
* If actor already created then just return it from the registry.
*
* Does not start the actor.
*/
private def createActor(name: String, uuid: String, timeout: Long): ActorRef = {
private def createActor(actorInfo: ActorInfoProtocol): ActorRef = {
val name = actorInfo.getTarget
val uuid = actorInfo.getUuid
val timeout = actorInfo.getTimeout
val actorRefOrNull = actors.get(uuid)
if (actorRefOrNull eq null) {
try {
@ -574,4 +529,43 @@ class RemoteServerHandler(
}
} else actorRefOrNull
}
private def createTypedActor(actorInfo: ActorInfoProtocol): AnyRef = {
val uuid = actorInfo.getUuid
val typedActorOrNull = typedActors.get(uuid)
if (typedActorOrNull eq null) {
val typedActorInfo = actorInfo.getTypedActorInfo
val interfaceClassname = typedActorInfo.getInterface
val targetClassname = actorInfo.getTarget
try {
log.info("Creating a new remote typed actor:\n\t[%s :: %s]", interfaceClassname, targetClassname)
val (interfaceClass, targetClass) =
if (applicationLoader.isDefined) (applicationLoader.get.loadClass(interfaceClassname),
applicationLoader.get.loadClass(targetClassname))
else (Class.forName(interfaceClassname), Class.forName(targetClassname))
val newInstance = TypedActor.newInstance(
interfaceClass, targetClass.asInstanceOf[Class[_ <: TypedActor]], actorInfo.getTimeout).asInstanceOf[AnyRef]
typedActors.put(uuid, newInstance)
newInstance
} catch {
case e => log.error(e, "Could not create remote typed actor instance"); throw e
}
} else typedActorOrNull
}
private def createErrorReplyMessage(e: Throwable, request: RemoteRequestProtocol, isActor: Boolean): RemoteReplyProtocol = {
val actorInfo = request.getActorInfo
log.error(e, "Could not invoke remote typed actor [%s :: %s]", actorInfo.getTypedActorInfo.getMethod, actorInfo.getTarget)
val replyBuilder = RemoteReplyProtocol.newBuilder
.setId(request.getId)
.setException(ExceptionProtocol.newBuilder.setClassname(e.getClass.getName).setMessage(e.getMessage).build)
.setIsSuccessful(false)
.setIsActor(isActor)
if (request.hasSupervisorUuid) replyBuilder.setSupervisorUuid(request.getSupervisorUuid)
replyBuilder.build
}
}

View file

@ -4,7 +4,9 @@
package se.scalablesolutions.akka.stm
import javax.transaction.{TransactionManager, UserTransaction, Transaction => JtaTransaction, SystemException, Status, Synchronization, TransactionSynchronizationRegistry}
import javax.transaction.{TransactionManager, UserTransaction,
Transaction => JtaTransaction, SystemException,
Status, Synchronization, TransactionSynchronizationRegistry}
import javax.naming.{InitialContext, Context, NamingException}
import se.scalablesolutions.akka.config.Config._
@ -16,7 +18,7 @@ import se.scalablesolutions.akka.util.Logging
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
object TransactionContainer extends Logging {
val AKKA_JTA_TRANSACTION_SERVICE_CLASS = "se.scalablesolutions.akka.jta.AtomikosTransactionService"
val AKKA_JTA_TRANSACTION_SERVICE_CLASS = "se.scalablesolutions.akka.jta.AtomikosTransactionService"
val DEFAULT_USER_TRANSACTION_NAME = "java:comp/UserTransaction"
val FALLBACK_TRANSACTION_MANAGER_NAMES = "java:comp/TransactionManager" ::
"java:appserver/TransactionManager" ::
@ -119,22 +121,31 @@ class TransactionContainer private (val tm: Either[Option[UserTransaction], Opti
}
}
def begin = tm match {
case Left(Some(userTx)) => userTx.begin
case Right(Some(txMan)) => txMan.begin
case _ => throw new StmConfigurationException("Does not have a UserTransaction or TransactionManager in scope")
def begin = {
TransactionContainer.log.ifTrace("Starting JTA transaction")
tm match {
case Left(Some(userTx)) => userTx.begin
case Right(Some(txMan)) => txMan.begin
case _ => throw new StmConfigurationException("Does not have a UserTransaction or TransactionManager in scope")
}
}
def commit = tm match {
case Left(Some(userTx)) => userTx.commit
case Right(Some(txMan)) => txMan.commit
case _ => throw new StmConfigurationException("Does not have a UserTransaction or TransactionManager in scope")
def commit = {
TransactionContainer.log.ifTrace("Committing JTA transaction")
tm match {
case Left(Some(userTx)) => userTx.commit
case Right(Some(txMan)) => txMan.commit
case _ => throw new StmConfigurationException("Does not have a UserTransaction or TransactionManager in scope")
}
}
def rollback = tm match {
case Left(Some(userTx)) => userTx.rollback
case Right(Some(txMan)) => txMan.rollback
case _ => throw new StmConfigurationException("Does not have a UserTransaction or TransactionManager in scope")
def rollback = {
TransactionContainer.log.ifTrace("Aborting JTA transaction")
tm match {
case Left(Some(userTx)) => userTx.rollback
case Right(Some(txMan)) => txMan.rollback
case _ => throw new StmConfigurationException("Does not have a UserTransaction or TransactionManager in scope")
}
}
def getStatus = tm match {

View file

@ -6,23 +6,15 @@ package se.scalablesolutions.akka.stm
import se.scalablesolutions.akka.util.UUID
import org.multiverse.api.GlobalStmInstance.getGlobalStmInstance
object RefFactory {
private val factory = getGlobalStmInstance.getProgrammaticRefFactoryBuilder.build
def createRef[T] = factory.atomicCreateRef[T]()
def createRef[T](value: T) = factory.atomicCreateRef(value)
}
import org.multiverse.transactional.refs.BasicRef
/**
* Ref.
* Ref
*
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
object Ref {
def apply[T]() = new Ref[T]
def apply[T]() = new Ref[T]()
def apply[T](initialValue: T) = new Ref[T](Some(initialValue))
@ -33,77 +25,47 @@ object Ref {
}
/**
* Implements a transactional managed reference.
* Transactional managed reference.
*
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
class Ref[T](initialOpt: Option[T] = None) extends Transactional {
class Ref[T](initialOpt: Option[T] = None)
extends BasicRef[T](initialOpt.getOrElse(null.asInstanceOf[T]))
with Transactional {
self =>
def this() = this(None) // Java compatibility
import org.multiverse.api.ThreadLocalTransaction._
val uuid = UUID.newUuid.toString
private[this] val ref = {
if (initialOpt.isDefined) RefFactory.createRef(initialOpt.get)
else RefFactory.createRef[T]
}
def swap(elem: T) = {
ensureIsInTransaction
ref.set(elem)
}
def swap(elem: T) = set(elem)
def alter(f: T => T): T = {
ensureIsInTransaction
ensureNotNull
ref.set(f(ref.get))
ref.get
set(f(this.get))
this.get
}
def get: Option[T] = {
ensureIsInTransaction
if (ref.isNull) None
else Some(ref.get)
}
def getOption: Option[T] = Option(this.get)
def getOrWait: T = {
ensureIsInTransaction
ref.getOrAwait
}
def getOrWait: T = getOrAwait
def getOrElse(default: => T): T = {
ensureIsInTransaction
if (ref.isNull) default
else ref.get
}
def getOrElse(default: => T): T =
if (isNull) default else this.get
def isDefined: Boolean = {
ensureIsInTransaction
!ref.isNull
}
def isDefined: Boolean = !isNull
def isEmpty: Boolean = {
ensureIsInTransaction
ref.isNull
}
def isEmpty: Boolean = isNull
def map[B](f: T => B): Ref[B] = {
ensureIsInTransaction
if (isEmpty) Ref[B] else Ref(f(ref.get))
}
def map[B](f: T => B): Ref[B] =
if (isEmpty) Ref[B] else Ref(f(this.get))
def flatMap[B](f: T => Ref[B]): Ref[B] = {
ensureIsInTransaction
if (isEmpty) Ref[B] else f(ref.get)
}
def flatMap[B](f: T => Ref[B]): Ref[B] =
if (isEmpty) Ref[B] else f(this.get)
def filter(p: T => Boolean): Ref[T] = {
ensureIsInTransaction
if (isDefined && p(ref.get)) Ref(ref.get) else Ref[T]
}
def filter(p: T => Boolean): Ref[T] =
if (isDefined && p(this.get)) Ref(this.get) else Ref[T]
/**
* Necessary to keep from being implicitly converted to Iterable in for comprehensions.
@ -117,34 +79,21 @@ class Ref[T](initialOpt: Option[T] = None) extends Transactional {
def withFilter(q: T => Boolean): WithFilter = new WithFilter(x => p(x) && q(x))
}
def foreach[U](f: T => U): Unit = {
ensureIsInTransaction
if (isDefined) f(ref.get)
}
def foreach[U](f: T => U): Unit =
if (isDefined) f(this.get)
def elements: Iterator[T] = {
ensureIsInTransaction
if (isEmpty) Iterator.empty else Iterator(ref.get)
}
def elements: Iterator[T] =
if (isEmpty) Iterator.empty else Iterator(this.get)
def toList: List[T] = {
ensureIsInTransaction
if (isEmpty) List() else List(ref.get)
}
def toList: List[T] =
if (isEmpty) List() else List(this.get)
def toRight[X](left: => X) = {
ensureIsInTransaction
if (isEmpty) Left(left) else Right(ref.get)
}
def toRight[X](left: => X) =
if (isEmpty) Left(left) else Right(this.get)
def toLeft[X](right: => X) = {
ensureIsInTransaction
if (isEmpty) Right(right) else Left(ref.get)
}
private def ensureIsInTransaction =
if (getThreadLocalTransaction eq null) throw new NoTransactionInScopeException
def toLeft[X](right: => X) =
if (isEmpty) Right(right) else Left(this.get)
private def ensureNotNull =
if (ref.isNull) throw new RuntimeException("Cannot alter Ref's value when it is null")
if (isNull) throw new RuntimeException("Cannot alter Ref's value when it is null")
}

View file

@ -83,11 +83,12 @@ object Transaction {
if (JTA_AWARE) Some(TransactionContainer())
else None
log.trace("Creating %s", toString)
log.ifTrace("Creating transaction " + toString)
// --- public methods ---------
def begin = synchronized {
log.ifTrace("Starting transaction " + toString)
jta.foreach { txContainer =>
txContainer.begin
txContainer.registerSynchronization(new StmSynchronization(txContainer, this))
@ -95,14 +96,14 @@ object Transaction {
}
def commit = synchronized {
log.trace("Committing transaction %s", toString)
log.ifTrace("Committing transaction " + toString)
persistentStateMap.valuesIterator.foreach(_.commit)
status = TransactionStatus.Completed
jta.foreach(_.commit)
}
def abort = synchronized {
log.trace("Aborting transaction %s", toString)
log.ifTrace("Aborting transaction " + toString)
jta.foreach(_.rollback)
persistentStateMap.valuesIterator.foreach(_.abort)
persistentStateMap.clear

View file

@ -22,8 +22,7 @@ object TransactionConfig {
val FAMILY_NAME = "DefaultTransaction"
val READONLY = null.asInstanceOf[JBoolean]
val MAX_RETRIES = config.getInt("akka.stm.max-retries", 1000)
val TIMEOUT = config.getLong("akka.stm.timeout", Long.MaxValue)
val TIME_UNIT = config.getString("akka.stm.time-unit", "seconds")
val TIMEOUT = config.getLong("akka.stm.timeout", 10)
val TRACK_READS = null.asInstanceOf[JBoolean]
val WRITE_SKEW = config.getBool("akka.stm.write-skew", true)
val EXPLICIT_RETRIES = config.getBool("akka.stm.explicit-retries", false)
@ -37,8 +36,8 @@ object TransactionConfig {
def traceLevel(level: String) = level.toLowerCase match {
case "coarse" | "course" => Transaction.TraceLevel.Coarse
case "fine" => Transaction.TraceLevel.Fine
case _ => Transaction.TraceLevel.None
case "fine" => Transaction.TraceLevel.Fine
case _ => Transaction.TraceLevel.None
}
/**
@ -125,8 +124,9 @@ object TransactionFactory {
quickRelease: Boolean = TransactionConfig.QUICK_RELEASE,
traceLevel: TraceLevel = TransactionConfig.TRACE_LEVEL,
hooks: Boolean = TransactionConfig.HOOKS) = {
val config = new TransactionConfig(familyName, readonly, maxRetries, timeout, trackReads, writeSkew,
explicitRetries, interruptible, speculative, quickRelease, traceLevel, hooks)
val config = new TransactionConfig(
familyName, readonly, maxRetries, timeout, trackReads, writeSkew,
explicitRetries, interruptible, speculative, quickRelease, traceLevel, hooks)
new TransactionFactory(config)
}
}
@ -152,8 +152,9 @@ object TransactionFactory {
*
* @see TransactionConfig for configuration options.
*/
class TransactionFactory(val config: TransactionConfig = DefaultTransactionConfig, defaultName: String = TransactionConfig.FAMILY_NAME) {
self =>
class TransactionFactory(
val config: TransactionConfig = DefaultTransactionConfig,
defaultName: String = TransactionConfig.FAMILY_NAME) { self =>
// use the config family name if it's been set, otherwise defaultName - used by actors to set class name as default
val familyName = if (config.familyName != TransactionConfig.FAMILY_NAME) config.familyName else defaultName

View file

@ -7,6 +7,7 @@ package se.scalablesolutions.akka.stm
import se.scalablesolutions.akka.util.Logging
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.TimeUnit
import org.multiverse.api.{StmUtils => MultiverseStmUtils}
import org.multiverse.api.ThreadLocalTransaction._
@ -14,16 +15,20 @@ import org.multiverse.api.{Transaction => MultiverseTransaction}
import org.multiverse.commitbarriers.CountDownCommitBarrier
import org.multiverse.templates.{TransactionalCallable, OrElseTemplate}
class StmException(msg: String) extends RuntimeException(msg)
class TransactionSetAbortedException(msg: String) extends RuntimeException(msg)
// TODO Should we remove TransactionAwareWrapperException? Not used anywhere yet.
class TransactionAwareWrapperException(val cause: Throwable, val tx: Option[Transaction]) extends RuntimeException(cause) {
override def toString = "TransactionAwareWrapperException[" + cause + ", " + tx + "]"
}
/**
* Internal helper methods and properties for transaction management.
*/
object TransactionManagement extends TransactionManagement {
import se.scalablesolutions.akka.config.Config._
// move to stm.global.fair?
// FIXME move to stm.global.fair?
val FAIR_TRANSACTIONS = config.getBool("akka.stm.fair", true)
private[akka] val transactionSet = new ThreadLocal[Option[CountDownCommitBarrier]]() {
@ -47,6 +52,9 @@ object TransactionManagement extends TransactionManagement {
}
}
/**
* Internal helper methods for transaction management.
*/
trait TransactionManagement {
private[akka] def createNewTransactionSet: CountDownCommitBarrier = {
@ -111,7 +119,9 @@ class LocalStm extends TransactionManagement with Logging {
factory.boilerplate.execute(new TransactionalCallable[T]() {
def call(mtx: MultiverseTransaction): T = {
factory.addHooks
body
val result = body
log.ifTrace("Committing local transaction [" + mtx + "]")
result
}
})
}
@ -145,10 +155,14 @@ class GlobalStm extends TransactionManagement with Logging {
factory.addHooks
val result = body
val txSet = getTransactionSetInScope
log.trace("Committing transaction [%s]\n\tby joining transaction set [%s]", mtx, txSet)
// FIXME ? txSet.tryJoinCommit(mtx, TransactionManagement.TRANSACTION_TIMEOUT, TimeUnit.MILLISECONDS)
try { txSet.joinCommit(mtx) } catch { case e: IllegalStateException => {} }
clearTransaction
log.ifTrace("Committing global transaction [" + mtx + "]\n\tand joining transaction set [" + txSet + "]")
try {
txSet.tryJoinCommit(
mtx,
TransactionConfig.DefaultTimeout.length,
TransactionConfig.DefaultTimeout.unit)
// Need to catch IllegalStateException until we have fix in Multiverse, since it throws it by mistake
} catch { case e: IllegalStateException => {} }
result
}
})
@ -156,18 +170,19 @@ class GlobalStm extends TransactionManagement with Logging {
}
trait StmUtil {
/**
* Schedule a deferred task on the thread local transaction (use within an atomic).
* This is executed when the transaction commits.
*/
def deferred[T](body: => T): Unit =
def deferred[T](body: => T): Unit =
MultiverseStmUtils.scheduleDeferredTask(new Runnable { def run = body })
/**
* Schedule a compensating task on the thread local transaction (use within an atomic).
* This is executed when the transaction aborts.
*/
def compensating[T](body: => T): Unit =
def compensating[T](body: => T): Unit =
MultiverseStmUtils.scheduleCompensatingTask(new Runnable { def run = body })
/**
@ -178,6 +193,14 @@ trait StmUtil {
/**
* Use either-orElse to combine two blocking transactions.
* Usage:
* <pre>
* either {
* ...
* } orElse {
* ...
* }
* </pre>
*/
def either[T](firstBody: => T) = new {
def orElse(secondBody: => T) = new OrElseTemplate[T] {

View file

@ -41,36 +41,36 @@ class TransactionalMap[K, V](initialOpt: Option[HashMap[K, V]] = None) extends T
}
override def remove(key: K) = {
val map = ref.get.get
val map = ref.get
val oldValue = map.get(key)
ref.swap(ref.get.get - key)
ref.swap(ref.get - key)
oldValue
}
def get(key: K): Option[V] = ref.get.get.get(key)
def get(key: K): Option[V] = ref.get.get(key)
override def put(key: K, value: V): Option[V] = {
val map = ref.get.get
val map = ref.get
val oldValue = map.get(key)
ref.swap(map.updated(key, value))
oldValue
}
override def update(key: K, value: V) = {
val map = ref.get.get
val map = ref.get
val oldValue = map.get(key)
ref.swap(map.updated(key, value))
}
def iterator = ref.get.get.iterator
def iterator = ref.get.iterator
override def elements: Iterator[(K, V)] = ref.get.get.iterator
override def elements: Iterator[(K, V)] = ref.get.iterator
override def contains(key: K): Boolean = ref.get.get.contains(key)
override def contains(key: K): Boolean = ref.get.contains(key)
override def clear = ref.swap(HashMap[K, V]())
override def size: Int = ref.get.get.size
override def size: Int = ref.get.size
override def hashCode: Int = System.identityHashCode(this);

Some files were not shown because too many files have changed in this diff Show more