Merge with master
This commit is contained in:
commit
98d3034a9a
271 changed files with 9230 additions and 4508 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -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
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
@ -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])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import java.lang.Throwable
|
|||
|
||||
private[amqp] class ConsumerActor(consumerParameters: ConsumerParameters)
|
||||
extends FaultTolerantChannelActor(consumerParameters.exchangeParameters, consumerParameters.channelParameters) {
|
||||
|
||||
|
||||
import consumerParameters._
|
||||
import exchangeParameters._
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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._
|
||||
|
||||
|
|
|
|||
|
|
@ -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+ "]"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,4 +31,4 @@ class RpcServerActor[I,O](producer: ActorRef, serializer: RpcServerSerializer[I,
|
|||
|
||||
override def toString(): String =
|
||||
"AMQP.RpcServer[]"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,4 +6,4 @@ package se.scalablesolutions.akka.amqp.test
|
|||
|
||||
object AMQPTest {
|
||||
def enabled = false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
@ -1 +0,0 @@
|
|||
class=se.scalablesolutions.akka.camel.component.ActiveObjectComponent
|
||||
|
|
@ -0,0 +1 @@
|
|||
class=se.scalablesolutions.akka.camel.component.TypedActorComponent
|
||||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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:<actorid></code>,
|
||||
* <code>actor:id:<actorid></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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
||||
/**
|
||||
|
|
@ -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) {
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package se.scalablesolutions.akka.camel;
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
public interface PojoNonConsumerIntf {
|
||||
|
||||
public String foo(String s);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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) {
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {}
|
||||
|
|
@ -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 {}
|
||||
|
|
@ -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 {}
|
||||
|
|
@ -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 {}
|
||||
|
|
@ -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 {}
|
||||
|
|
@ -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 {}
|
||||
|
|
@ -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 {}
|
||||
|
|
@ -13,10 +13,10 @@ import com.google.inject.Singleton;
|
|||
/**
|
||||
* @author <a href="http://jonasboner.com">Jonas Boné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;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<aspectwerkz>
|
||||
<system id="akka">
|
||||
<package name="se.scalablesolutions.akka.actor">
|
||||
<aspect class="ActiveObjectAspect" />
|
||||
<aspect class="TypedActorAspect" />
|
||||
</package>
|
||||
</system>
|
||||
</aspectwerkz>
|
||||
|
|
|
|||
|
|
@ -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é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é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é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é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é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é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]])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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é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é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é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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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é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é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
|
||||
|
|
|
|||
|
|
@ -29,11 +29,6 @@ case class ActorUnregistered(actor: ActorRef) extends ActorRegistryEvent
|
|||
* @author <a href="http://jonasboner.com">Jonas Boné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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 + "]")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
804
akka-core/src/main/scala/actor/TypedActor.scala
Normal file
804
akka-core/src/main/scala/actor/TypedActor.scala
Normal 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é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ér</a>
|
||||
*/
|
||||
abstract class TypedTransactor extends TypedActor
|
||||
|
||||
/**
|
||||
* Configuration factory for TypedActors.
|
||||
*
|
||||
* FIXDOC: document TypedActorConfiguration
|
||||
*
|
||||
* @author <a href="http://jonasboner.com">Jonas Boné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é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é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é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é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é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é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é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é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]])
|
||||
}
|
||||
}
|
||||
}
|
||||
580
akka-core/src/main/scala/actor/UntypedActor.scala
Normal file
580
akka-core/src/main/scala/actor/UntypedActor.scala
Normal 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é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é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é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é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é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é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))
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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é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
|
||||
}
|
||||
|
|
@ -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é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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
249
akka-core/src/main/scala/dispatch/HawtDispatcher.scala
Normal file
249
akka-core/src/main/scala/dispatch/HawtDispatcher.scala
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ import se.scalablesolutions.akka.actor.IllegalActorStateException
|
|||
* @author <a href="http://jonasboner.com">Jonas Boné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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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é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) = {
|
||||
|
|
|
|||
|
|
@ -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é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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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é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 {
|
||||
|
|
|
|||
|
|
@ -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é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é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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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] {
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue