Merge branch 'master' into fsm
This commit is contained in:
commit
fd35b7a88f
360 changed files with 12294 additions and 7085 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -18,9 +18,11 @@ deploy/*.jar
|
|||
data
|
||||
out
|
||||
logs
|
||||
.#*
|
||||
.codefellow
|
||||
storage
|
||||
.codefellow
|
||||
.ensime
|
||||
_dump
|
||||
.manager
|
||||
manifest.mf
|
||||
|
|
@ -39,4 +41,5 @@ run-codefellow
|
|||
.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);
|
||||
|
|
@ -1,143 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
||||
*/
|
||||
|
||||
package se.scalablesolutions.akka.amqp
|
||||
|
||||
import se.scalablesolutions.akka.actor.{Actor, ActorRef}
|
||||
import se.scalablesolutions.akka.actor.Actor._
|
||||
import se.scalablesolutions.akka.config.OneForOneStrategy
|
||||
import com.rabbitmq.client.{ReturnListener, ShutdownListener, ConnectionFactory}
|
||||
import java.lang.IllegalArgumentException
|
||||
import se.scalablesolutions.akka.util.Logging
|
||||
/**
|
||||
* AMQP Actor API. Implements Connection, Producer and Consumer materialized as Actors.
|
||||
*
|
||||
* @see se.scalablesolutions.akka.amqp.ExampleSession
|
||||
*
|
||||
* @author Irmo Manie
|
||||
*/
|
||||
object AMQP {
|
||||
case class ConnectionParameters(
|
||||
host: String = ConnectionFactory.DEFAULT_HOST,
|
||||
port: Int = ConnectionFactory.DEFAULT_AMQP_PORT,
|
||||
username: String = ConnectionFactory.DEFAULT_USER,
|
||||
password: String = ConnectionFactory.DEFAULT_PASS,
|
||||
virtualHost: String = ConnectionFactory.DEFAULT_VHOST,
|
||||
initReconnectDelay: Long = 5000,
|
||||
connectionCallback: Option[ActorRef] = None)
|
||||
|
||||
case class ChannelParameters(
|
||||
shutdownListener: Option[ShutdownListener] = None,
|
||||
channelCallback: Option[ActorRef] = None)
|
||||
|
||||
case class ExchangeParameters(
|
||||
exchangeName: String,
|
||||
exchangeType: ExchangeType,
|
||||
exchangeDurable: Boolean = false,
|
||||
exchangeAutoDelete: Boolean = true,
|
||||
exchangePassive: Boolean = false,
|
||||
configurationArguments: Map[String, AnyRef] = Map())
|
||||
|
||||
case class ProducerParameters(exchangeParameters: ExchangeParameters,
|
||||
producerId: Option[String] = None,
|
||||
returnListener: Option[ReturnListener] = None,
|
||||
channelParameters: Option[ChannelParameters] = None)
|
||||
|
||||
case class ConsumerParameters(exchangeParameters: ExchangeParameters,
|
||||
routingKey: String,
|
||||
deliveryHandler: ActorRef,
|
||||
queueName: Option[String] = None,
|
||||
queueDurable: Boolean = false,
|
||||
queueAutoDelete: Boolean = true,
|
||||
queuePassive: Boolean = false,
|
||||
queueExclusive: Boolean = false,
|
||||
selfAcknowledging: Boolean = true,
|
||||
channelParameters: Option[ChannelParameters] = None) {
|
||||
if (queueDurable && queueName.isEmpty) {
|
||||
throw new IllegalArgumentException("A queue name is required when requesting a durable queue.")
|
||||
}
|
||||
}
|
||||
|
||||
def newConnection(connectionParameters: ConnectionParameters = new ConnectionParameters): ActorRef = {
|
||||
val connection: ActorRef = supervisor.newConnection(connectionParameters)
|
||||
connection ! Connect
|
||||
connection
|
||||
}
|
||||
|
||||
def newProducer(connection: ActorRef, producerParameters: ProducerParameters): ActorRef = {
|
||||
val producer: ActorRef = Actor.actorOf(new ProducerActor(producerParameters))
|
||||
connection.startLink(producer)
|
||||
producer ! Start
|
||||
producer
|
||||
}
|
||||
|
||||
def newConsumer(connection: ActorRef, consumerParameters: ConsumerParameters): ActorRef = {
|
||||
val consumer: ActorRef = actorOf(new ConsumerActor(consumerParameters))
|
||||
consumer.startLink(consumerParameters.deliveryHandler)
|
||||
connection.startLink(consumer)
|
||||
consumer ! Start
|
||||
consumer
|
||||
}
|
||||
|
||||
def newRpcClient[O,I](connection: ActorRef,
|
||||
exchangeParameters: ExchangeParameters,
|
||||
routingKey: String,
|
||||
serializer: RpcClientSerializer[O,I],
|
||||
channelParameters: Option[ChannelParameters] = None): ActorRef = {
|
||||
val rpcActor: ActorRef = actorOf(new RpcClientActor[O,I](exchangeParameters, routingKey, serializer, channelParameters))
|
||||
connection.startLink(rpcActor)
|
||||
rpcActor ! Start
|
||||
rpcActor
|
||||
}
|
||||
|
||||
def newRpcServer[I,O](connection: ActorRef,
|
||||
exchangeParameters: ExchangeParameters,
|
||||
routingKey: String,
|
||||
serializer: RpcServerSerializer[I,O],
|
||||
requestHandler: PartialFunction[I, O],
|
||||
channelParameters: Option[ChannelParameters] = None) = {
|
||||
val producer = newProducer(connection, new ProducerParameters(new ExchangeParameters("", ExchangeType.Direct), channelParameters = channelParameters))
|
||||
val rpcServer = actorOf(new RpcServerActor[I,O](producer, serializer, requestHandler))
|
||||
val consumer = newConsumer(connection, new ConsumerParameters(exchangeParameters, routingKey, rpcServer
|
||||
, channelParameters = channelParameters
|
||||
, selfAcknowledging = false))
|
||||
|
||||
}
|
||||
|
||||
private val supervisor = new AMQPSupervisor
|
||||
|
||||
class AMQPSupervisor extends Logging {
|
||||
class AMQPSupervisorActor extends Actor {
|
||||
import self._
|
||||
|
||||
faultHandler = Some(OneForOneStrategy(5, 5000))
|
||||
trapExit = List(classOf[Throwable])
|
||||
|
||||
def receive = {
|
||||
case _ => {} // ignore all messages
|
||||
}
|
||||
}
|
||||
|
||||
private val supervisor = actorOf(new AMQPSupervisorActor).start
|
||||
|
||||
def newConnection(connectionParameters: ConnectionParameters): ActorRef = {
|
||||
val connectionActor = actorOf(new FaultTolerantConnectionActor(connectionParameters))
|
||||
supervisor.startLink(connectionActor)
|
||||
connectionActor
|
||||
}
|
||||
}
|
||||
|
||||
trait FromBinary[T] {
|
||||
def fromBinary(bytes: Array[Byte]): T
|
||||
}
|
||||
|
||||
trait ToBinary[T] {
|
||||
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])
|
||||
}
|
||||
|
|
@ -0,0 +1,218 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
||||
*/
|
||||
|
||||
package se.scalablesolutions.akka.amqp
|
||||
|
||||
import se.scalablesolutions.akka.actor.{Actor, ActorRef}
|
||||
import se.scalablesolutions.akka.actor.Actor._
|
||||
import se.scalablesolutions.akka.config.OneForOneStrategy
|
||||
import com.rabbitmq.client.{ReturnListener, ShutdownListener, ConnectionFactory}
|
||||
import com.rabbitmq.client.AMQP.BasicProperties
|
||||
import java.lang.{String, IllegalArgumentException}
|
||||
|
||||
/**
|
||||
* AMQP Actor API. Implements Connection, Producer and Consumer materialized as Actors.
|
||||
*
|
||||
* @see se.scalablesolutions.akka.amqp.ExampleSession
|
||||
*
|
||||
* @author Irmo Manie
|
||||
*/
|
||||
object AMQP {
|
||||
case class ConnectionParameters(
|
||||
host: String = ConnectionFactory.DEFAULT_HOST,
|
||||
port: Int = ConnectionFactory.DEFAULT_AMQP_PORT,
|
||||
username: String = ConnectionFactory.DEFAULT_USER,
|
||||
password: String = ConnectionFactory.DEFAULT_PASS,
|
||||
virtualHost: String = ConnectionFactory.DEFAULT_VHOST,
|
||||
initReconnectDelay: Long = 5000,
|
||||
connectionCallback: Option[ActorRef] = None)
|
||||
|
||||
case class ChannelParameters(
|
||||
shutdownListener: Option[ShutdownListener] = None,
|
||||
channelCallback: Option[ActorRef] = None)
|
||||
|
||||
case class ExchangeParameters(
|
||||
exchangeName: String,
|
||||
exchangeType: ExchangeType,
|
||||
exchangeDurable: Boolean = false,
|
||||
exchangeAutoDelete: Boolean = true,
|
||||
exchangePassive: Boolean = false,
|
||||
configurationArguments: Map[String, AnyRef] = Map())
|
||||
|
||||
case class ProducerParameters(
|
||||
exchangeParameters: ExchangeParameters,
|
||||
producerId: Option[String] = None,
|
||||
returnListener: Option[ReturnListener] = None,
|
||||
channelParameters: Option[ChannelParameters] = None)
|
||||
|
||||
case class ConsumerParameters(
|
||||
exchangeParameters: ExchangeParameters,
|
||||
routingKey: String,
|
||||
deliveryHandler: ActorRef,
|
||||
queueName: Option[String] = None,
|
||||
queueDurable: Boolean = false,
|
||||
queueAutoDelete: Boolean = true,
|
||||
queuePassive: Boolean = false,
|
||||
queueExclusive: Boolean = false,
|
||||
selfAcknowledging: Boolean = true,
|
||||
channelParameters: Option[ChannelParameters] = None) {
|
||||
if (queueDurable && queueName.isEmpty) {
|
||||
throw new IllegalArgumentException("A queue name is required when requesting a durable queue.")
|
||||
}
|
||||
}
|
||||
|
||||
def newConnection(connectionParameters: ConnectionParameters = new ConnectionParameters): ActorRef = {
|
||||
val connection = actorOf(new FaultTolerantConnectionActor(connectionParameters))
|
||||
supervisor.startLink(connection)
|
||||
connection ! Connect
|
||||
connection
|
||||
}
|
||||
|
||||
def newProducer(connection: ActorRef, producerParameters: ProducerParameters): ActorRef = {
|
||||
val producer: ActorRef = Actor.actorOf(new ProducerActor(producerParameters))
|
||||
connection.startLink(producer)
|
||||
producer ! Start
|
||||
producer
|
||||
}
|
||||
|
||||
def newConsumer(connection: ActorRef, consumerParameters: ConsumerParameters): ActorRef = {
|
||||
val consumer: ActorRef = actorOf(new ConsumerActor(consumerParameters))
|
||||
val handler = consumerParameters.deliveryHandler
|
||||
if (handler.supervisor.isEmpty) consumer.startLink(handler)
|
||||
connection.startLink(consumer)
|
||||
consumer ! Start
|
||||
consumer
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience
|
||||
*/
|
||||
class ProducerClient[O](client: ActorRef, routingKey: String, toBinary: ToBinary[O]) {
|
||||
def send(request: O, replyTo: Option[String] = None) = {
|
||||
val basicProperties = new BasicProperties
|
||||
basicProperties.setReplyTo(replyTo.getOrElse(null))
|
||||
client ! Message(toBinary.toBinary(request), routingKey, false, false, Some(basicProperties))
|
||||
}
|
||||
|
||||
def stop = client.stop
|
||||
}
|
||||
|
||||
def newStringProducer(connection: ActorRef,
|
||||
exchange: String,
|
||||
routingKey: Option[String] = None,
|
||||
producerId: Option[String] = None,
|
||||
durable: Boolean = false,
|
||||
autoDelete: Boolean = true,
|
||||
passive: Boolean = true): ProducerClient[String] = {
|
||||
|
||||
val exchangeParameters = ExchangeParameters(exchange, ExchangeType.Topic,
|
||||
exchangeDurable = durable, exchangeAutoDelete = autoDelete)
|
||||
val rKey = routingKey.getOrElse("%s.request".format(exchange))
|
||||
|
||||
val producerRef = newProducer(connection, ProducerParameters(exchangeParameters, producerId))
|
||||
val toBinary = new ToBinary[String] {
|
||||
def toBinary(t: String) = t.getBytes
|
||||
}
|
||||
new ProducerClient(producerRef, rKey, toBinary)
|
||||
}
|
||||
|
||||
def newStringConsumer(connection: ActorRef,
|
||||
exchange: String,
|
||||
handler: String => Unit,
|
||||
routingKey: Option[String] = None,
|
||||
queueName: Option[String] = None,
|
||||
durable: Boolean = false,
|
||||
autoDelete: Boolean = true): ActorRef = {
|
||||
|
||||
val deliveryHandler = actor {
|
||||
case Delivery(payload, _, _, _, _) => handler.apply(new String(payload))
|
||||
}
|
||||
|
||||
val exchangeParameters = ExchangeParameters(exchange, ExchangeType.Topic,
|
||||
exchangeDurable = durable, exchangeAutoDelete = autoDelete)
|
||||
val rKey = routingKey.getOrElse("%s.request".format(exchange))
|
||||
val qName = queueName.getOrElse("%s.in".format(rKey))
|
||||
|
||||
newConsumer(connection, ConsumerParameters(exchangeParameters, rKey, deliveryHandler, Some(qName), durable, autoDelete))
|
||||
}
|
||||
|
||||
def newProtobufProducer[O <: com.google.protobuf.Message](connection: ActorRef,
|
||||
exchange: String,
|
||||
routingKey: Option[String] = None,
|
||||
producerId: Option[String] = None,
|
||||
durable: Boolean = false,
|
||||
autoDelete: Boolean = true,
|
||||
passive: Boolean = true): ProducerClient[O] = {
|
||||
|
||||
val exchangeParameters = ExchangeParameters(exchange, ExchangeType.Topic,
|
||||
exchangeDurable = durable, exchangeAutoDelete = autoDelete)
|
||||
val rKey = routingKey.getOrElse("%s.request".format(exchange))
|
||||
|
||||
val producerRef = newProducer(connection, ProducerParameters(exchangeParameters, producerId))
|
||||
new ProducerClient(producerRef, rKey, new ToBinary[O] {
|
||||
def toBinary(t: O) = t.toByteArray
|
||||
})
|
||||
}
|
||||
|
||||
def newProtobufConsumer[I <: com.google.protobuf.Message](connection: ActorRef,
|
||||
exchange: String,
|
||||
handler: I => Unit,
|
||||
routingKey: Option[String] = None,
|
||||
queueName: Option[String] = None,
|
||||
durable: Boolean = false,
|
||||
autoDelete: Boolean = true)(implicit manifest: Manifest[I]): ActorRef = {
|
||||
|
||||
val deliveryHandler = actor {
|
||||
case Delivery(payload, _, _, _, _) => {
|
||||
handler.apply(createProtobufFromBytes[I](payload))
|
||||
}
|
||||
}
|
||||
|
||||
val exchangeParameters = ExchangeParameters(exchange, ExchangeType.Topic,
|
||||
exchangeDurable = durable, exchangeAutoDelete = autoDelete)
|
||||
val rKey = routingKey.getOrElse("%s.request".format(exchange))
|
||||
val qName = queueName.getOrElse("%s.in".format(rKey))
|
||||
|
||||
newConsumer(connection, ConsumerParameters(exchangeParameters, rKey, deliveryHandler, Some(qName), durable, autoDelete))
|
||||
}
|
||||
|
||||
/**
|
||||
* Main supervisor
|
||||
*/
|
||||
|
||||
class AMQPSupervisorActor extends Actor {
|
||||
import self._
|
||||
|
||||
faultHandler = Some(OneForOneStrategy(5, 5000))
|
||||
trapExit = List(classOf[Throwable])
|
||||
|
||||
def receive = {
|
||||
case _ => {} // ignore all messages
|
||||
}
|
||||
}
|
||||
|
||||
private val supervisor = actorOf(new AMQPSupervisorActor).start
|
||||
|
||||
def shutdownAll = {
|
||||
supervisor.shutdownLinkedActors
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialization stuff
|
||||
*/
|
||||
|
||||
trait FromBinary[T] {
|
||||
def fromBinary(bytes: Array[Byte]): T
|
||||
}
|
||||
|
||||
trait ToBinary[T] {
|
||||
def toBinary(t: T): Array[Byte]
|
||||
}
|
||||
|
||||
private val ARRAY_OF_BYTE_ARRAY = Array[Class[_]](classOf[Array[Byte]])
|
||||
|
||||
private[amqp] def createProtobufFromBytes[I <: com.google.protobuf.Message](bytes: Array[Byte])(implicit manifest: Manifest[I]): I = {
|
||||
manifest.erasure.getDeclaredMethod("parseFrom", ARRAY_OF_BYTE_ARRAY: _*).invoke(null, bytes).asInstanceOf[I]
|
||||
}
|
||||
}
|
||||
|
|
@ -5,25 +5,27 @@
|
|||
package se.scalablesolutions.akka.amqp
|
||||
|
||||
import se.scalablesolutions.akka.actor.ActorRef
|
||||
import se.scalablesolutions.akka.AkkaException
|
||||
|
||||
import com.rabbitmq.client.AMQP.BasicProperties
|
||||
import com.rabbitmq.client.ShutdownSignalException
|
||||
|
||||
sealed trait AMQPMessage
|
||||
sealed trait InternalAMQPMessage extends AMQPMessage
|
||||
|
||||
case class Message(payload: Array[Byte],
|
||||
routingKey: String,
|
||||
mandatory: Boolean = false,
|
||||
immediate: Boolean = false,
|
||||
properties: Option[BasicProperties] = None) extends AMQPMessage
|
||||
|
||||
case class Delivery(payload: Array[Byte],
|
||||
routingKey: String,
|
||||
deliveryTag: Long,
|
||||
properties: BasicProperties,
|
||||
sender: Option[ActorRef]) extends AMQPMessage
|
||||
|
||||
case class Message(
|
||||
payload: Array[Byte],
|
||||
routingKey: String,
|
||||
mandatory: Boolean = false,
|
||||
immediate: Boolean = false,
|
||||
properties: Option[BasicProperties] = None) extends AMQPMessage
|
||||
|
||||
case class Delivery(
|
||||
payload: Array[Byte],
|
||||
routingKey: String,
|
||||
deliveryTag: Long,
|
||||
properties: BasicProperties,
|
||||
sender: Option[ActorRef]) extends AMQPMessage
|
||||
|
||||
// connection messages
|
||||
case object Connect extends AMQPMessage
|
||||
|
|
@ -44,6 +46,9 @@ case object Stopped extends AMQPMessage
|
|||
// delivery messages
|
||||
case class Acknowledge(deliveryTag: Long) extends AMQPMessage
|
||||
case class Acknowledged(deliveryTag: Long) extends AMQPMessage
|
||||
case class Reject(deliveryTag: Long) extends AMQPMessage
|
||||
case class Rejected(deliveryTag: Long) extends AMQPMessage
|
||||
class RejectionException(deliveryTag: Long) extends RuntimeException
|
||||
|
||||
// internal messages
|
||||
private[akka] case class Failure(cause: Throwable) extends InternalAMQPMessage
|
||||
|
|
@ -51,10 +56,10 @@ private[akka] case class ConnectionShutdown(cause: ShutdownSignalException) exte
|
|||
private[akka] case class ChannelShutdown(cause: ShutdownSignalException) extends InternalAMQPMessage
|
||||
|
||||
private[akka] class MessageNotDeliveredException(
|
||||
val message: String,
|
||||
val replyCode: Int,
|
||||
val replyText: String,
|
||||
val exchange: String,
|
||||
val routingKey: String,
|
||||
val properties: BasicProperties,
|
||||
val body: Array[Byte]) extends RuntimeException(message)
|
||||
val message: String,
|
||||
val replyCode: Int,
|
||||
val replyText: String,
|
||||
val exchange: String,
|
||||
val routingKey: String,
|
||||
val properties: BasicProperties,
|
||||
val body: Array[Byte]) extends RuntimeException(message)
|
||||
|
|
@ -4,17 +4,20 @@
|
|||
|
||||
package se.scalablesolutions.akka.amqp
|
||||
|
||||
import com.rabbitmq.client.AMQP.Queue.DeclareOk
|
||||
import collection.JavaConversions
|
||||
|
||||
import se.scalablesolutions.akka.amqp.AMQP.ConsumerParameters
|
||||
import se.scalablesolutions.akka.util.Logging
|
||||
import com.rabbitmq.client.{Channel, Envelope, DefaultConsumer}
|
||||
import se.scalablesolutions.akka.AkkaException
|
||||
|
||||
import com.rabbitmq.client.AMQP.Queue.DeclareOk
|
||||
import com.rabbitmq.client.AMQP.BasicProperties
|
||||
import java.lang.Throwable
|
||||
import com.rabbitmq.client.{Channel, Envelope, DefaultConsumer}
|
||||
|
||||
private[amqp] class ConsumerActor(consumerParameters: ConsumerParameters)
|
||||
extends FaultTolerantChannelActor(consumerParameters.exchangeParameters, consumerParameters.channelParameters) {
|
||||
|
||||
extends FaultTolerantChannelActor(
|
||||
consumerParameters.exchangeParameters, consumerParameters.channelParameters) {
|
||||
|
||||
import consumerParameters._
|
||||
import exchangeParameters._
|
||||
|
||||
|
|
@ -22,6 +25,7 @@ private[amqp] class ConsumerActor(consumerParameters: ConsumerParameters)
|
|||
|
||||
def specificMessageHandler = {
|
||||
case Acknowledge(deliveryTag) => acknowledgeDeliveryTag(deliveryTag, true)
|
||||
case Reject(deliveryTag) => rejectDeliveryTag(deliveryTag, true)
|
||||
case message: Message =>
|
||||
handleIllegalMessage("%s can't be used to send messages, ignoring message [%s]".format(this, message))
|
||||
case unknown =>
|
||||
|
|
@ -34,10 +38,11 @@ private[amqp] class ConsumerActor(consumerParameters: ConsumerParameters)
|
|||
queueName match {
|
||||
case Some(name) =>
|
||||
log.debug("Declaring new queue [%s] for %s", name, toString)
|
||||
if (queuePassive) {
|
||||
ch.queueDeclarePassive(name)
|
||||
} else {
|
||||
ch.queueDeclare(name, queueDurable, queueExclusive, queueAutoDelete, JavaConversions.asMap(configurationArguments))
|
||||
if (queuePassive) ch.queueDeclarePassive(name)
|
||||
else {
|
||||
ch.queueDeclare(
|
||||
name, queueDurable, queueExclusive, queueAutoDelete,
|
||||
JavaConversions.asMap(configurationArguments))
|
||||
}
|
||||
case None =>
|
||||
log.debug("Declaring new generated queue for %s", toString)
|
||||
|
|
@ -80,12 +85,24 @@ private[amqp] class ConsumerActor(consumerParameters: ConsumerParameters)
|
|||
}
|
||||
}
|
||||
|
||||
private def rejectDeliveryTag(deliveryTag: Long, remoteAcknowledgement: Boolean) = {
|
||||
log.debug("Rejecting message with delivery tag [%s]", deliveryTag)
|
||||
// FIXME: when rabbitmq 1.9 arrives, basicReject should be available on the API and implemented instead of this
|
||||
log.warning("Consumer is rejecting delivery with tag [%s] - " +
|
||||
"for now this means we have to self terminate and kill the channel - see you in a second.")
|
||||
channel.foreach{ch =>
|
||||
if (remoteAcknowledgement) {
|
||||
deliveryHandler ! Rejected(deliveryTag)
|
||||
}
|
||||
}
|
||||
throw new RejectionException(deliveryTag)
|
||||
}
|
||||
|
||||
private def handleIllegalMessage(errorMessage: String) = {
|
||||
log.error(errorMessage)
|
||||
throw new IllegalArgumentException(errorMessage)
|
||||
}
|
||||
|
||||
|
||||
override def preRestart(reason: Throwable) = {
|
||||
listenerTag = None
|
||||
super.preRestart(reason)
|
||||
|
|
@ -93,15 +110,15 @@ private[amqp] class ConsumerActor(consumerParameters: ConsumerParameters)
|
|||
|
||||
override def shutdown = {
|
||||
listenerTag.foreach(tag => channel.foreach(_.basicCancel(tag)))
|
||||
self.linkedActorsAsList.foreach(_.stop)
|
||||
self.shutdownLinkedActors
|
||||
super.shutdown
|
||||
}
|
||||
|
||||
override def toString(): String =
|
||||
override def toString =
|
||||
"AMQP.Consumer[id= "+ self.id +
|
||||
", exchange=" + exchangeName +
|
||||
", exchangeType=" + exchangeType +
|
||||
", durable=" + exchangeDurable +
|
||||
", autoDelete=" + exchangeAutoDelete + "]"
|
||||
", exchange=" + exchangeName +
|
||||
", exchangeType=" + exchangeType +
|
||||
", durable=" + exchangeDurable +
|
||||
", autoDelete=" + exchangeAutoDelete + "]"
|
||||
}
|
||||
|
||||
|
|
@ -4,44 +4,64 @@
|
|||
|
||||
package se.scalablesolutions.akka.amqp
|
||||
|
||||
import rpc.RPC
|
||||
import rpc.RPC.{RpcClientSerializer, RpcServerSerializer}
|
||||
import se.scalablesolutions.akka.actor.{Actor, ActorRegistry}
|
||||
import Actor._
|
||||
import java.util.concurrent.{CountDownLatch, TimeUnit}
|
||||
import se.scalablesolutions.akka.amqp.AMQP._
|
||||
import java.lang.String
|
||||
import se.scalablesolutions.akka.amqp.AMQP._
|
||||
import se.scalablesolutions.akka.remote.protocol.RemoteProtocol.AddressProtocol
|
||||
|
||||
object ExampleSession {
|
||||
|
||||
def main(args: Array[String]) = {
|
||||
println("==== DIRECT ===")
|
||||
|
||||
printTopic("DIRECT")
|
||||
direct
|
||||
|
||||
TimeUnit.SECONDS.sleep(2)
|
||||
|
||||
println("==== FANOUT ===")
|
||||
printTopic("FANOUT")
|
||||
fanout
|
||||
|
||||
TimeUnit.SECONDS.sleep(2)
|
||||
|
||||
println("==== TOPIC ===")
|
||||
printTopic("TOPIC")
|
||||
topic
|
||||
|
||||
TimeUnit.SECONDS.sleep(2)
|
||||
|
||||
println("==== CALLBACK ===")
|
||||
printTopic("CALLBACK")
|
||||
callback
|
||||
|
||||
TimeUnit.SECONDS.sleep(2)
|
||||
printTopic("EASY STRING PRODUCER AND CONSUMER")
|
||||
easyStringProducerConsumer
|
||||
|
||||
println("==== RPC ===")
|
||||
printTopic("EASY PROTOBUF PRODUCER AND CONSUMER")
|
||||
easyProtobufProducerConsumer
|
||||
|
||||
printTopic("RPC")
|
||||
rpc
|
||||
|
||||
TimeUnit.SECONDS.sleep(2)
|
||||
printTopic("EASY STRING RPC")
|
||||
easyStringRpc
|
||||
|
||||
printTopic("EASY PROTOBUF RPC")
|
||||
easyProtobufRpc
|
||||
|
||||
printTopic("Happy hAkking :-)")
|
||||
|
||||
// shutdown everything the amqp tree except the main AMQP supervisor
|
||||
// all connections/consumers/producers will be stopped
|
||||
AMQP.shutdownAll
|
||||
|
||||
ActorRegistry.shutdownAll
|
||||
System.exit(0)
|
||||
}
|
||||
|
||||
def printTopic(topic: String) {
|
||||
|
||||
println("")
|
||||
println("==== " + topic + " ===")
|
||||
println("")
|
||||
TimeUnit.SECONDS.sleep(2)
|
||||
}
|
||||
|
||||
def direct = {
|
||||
|
||||
// defaults to amqp://guest:guest@localhost:5672/
|
||||
|
|
@ -115,7 +135,7 @@ object ExampleSession {
|
|||
case Restarting => // not used, sent when channel or connection fails and initiates a restart
|
||||
case Stopped => log.info("Channel callback: Stopped")
|
||||
}
|
||||
val exchangeParameters = ExchangeParameters("my_direct_exchange", ExchangeType.Direct)
|
||||
val exchangeParameters = ExchangeParameters("my_callback_exchange", ExchangeType.Direct)
|
||||
val channelParameters = ChannelParameters(channelCallback = Some(channelCallback))
|
||||
|
||||
val consumer = AMQP.newConsumer(connection, ConsumerParameters(exchangeParameters, "callback.routing", actor {
|
||||
|
|
@ -129,6 +149,40 @@ object ExampleSession {
|
|||
connection.stop
|
||||
}
|
||||
|
||||
def easyStringProducerConsumer = {
|
||||
val connection = AMQP.newConnection()
|
||||
|
||||
val exchangeName = "easy.string"
|
||||
|
||||
// listen by default to:
|
||||
// exchange = exchangeName
|
||||
// routingKey = <exchange>.request
|
||||
// queueName = <routingKey>.in
|
||||
AMQP.newStringConsumer(connection, exchangeName, message => println("Received message: "+message))
|
||||
|
||||
// send by default to:
|
||||
// exchange = exchangeName
|
||||
// routingKey = <exchange>.request
|
||||
val producer = AMQP.newStringProducer(connection, exchangeName)
|
||||
|
||||
producer.send("This shit is easy!")
|
||||
}
|
||||
|
||||
def easyProtobufProducerConsumer = {
|
||||
val connection = AMQP.newConnection()
|
||||
|
||||
val exchangeName = "easy.protobuf"
|
||||
|
||||
def protobufMessageHandler(message: AddressProtocol) = {
|
||||
log.info("Received "+message)
|
||||
}
|
||||
|
||||
AMQP.newProtobufConsumer(connection, exchangeName, protobufMessageHandler)
|
||||
|
||||
val producerClient = AMQP.newProtobufProducer[AddressProtocol](connection, exchangeName)
|
||||
producerClient.send(AddressProtocol.newBuilder.setHostname("akkarocks.com").setPort(1234).build)
|
||||
}
|
||||
|
||||
def rpc = {
|
||||
|
||||
val connection = AMQP.newConnection()
|
||||
|
|
@ -144,10 +198,10 @@ object ExampleSession {
|
|||
}
|
||||
val rpcServerSerializer = new RpcServerSerializer[String, Int](serverFromBinary, serverToBinary)
|
||||
|
||||
val rpcServer = AMQP.newRpcServer[String,Int](connection, exchangeParameters, "rpc.in.key", rpcServerSerializer, {
|
||||
case "rpc_request" => 3
|
||||
case _ => error("unknown request")
|
||||
})
|
||||
def requestHandler(request: String) = 3
|
||||
|
||||
val rpcServer = RPC.newRpcServer[String,Int](connection, exchangeParameters, "rpc.in.key", rpcServerSerializer,
|
||||
requestHandler, queueName = Some("rpc.in.key.queue"))
|
||||
|
||||
|
||||
/** Client */
|
||||
|
|
@ -159,9 +213,56 @@ object ExampleSession {
|
|||
}
|
||||
val rpcClientSerializer = new RpcClientSerializer[String, Int](clientToBinary, clientFromBinary)
|
||||
|
||||
val rpcClient = AMQP.newRpcClient[String,Int](connection, exchangeParameters, "rpc.in.key", rpcClientSerializer)
|
||||
val rpcClient = RPC.newRpcClient[String,Int](connection, exchangeParameters, "rpc.in.key", rpcClientSerializer)
|
||||
|
||||
val response = (rpcClient !! "rpc_request")
|
||||
log.info("Response: " + response)
|
||||
}
|
||||
|
||||
def easyStringRpc = {
|
||||
|
||||
val connection = AMQP.newConnection()
|
||||
|
||||
val exchangeName = "easy.stringrpc"
|
||||
|
||||
// listen by default to:
|
||||
// exchange = exchangeName
|
||||
// routingKey = <exchange>.request
|
||||
// queueName = <routingKey>.in
|
||||
RPC.newStringRpcServer(connection, exchangeName, request => {
|
||||
log.info("Got request: "+request)
|
||||
"Response to: '"+request+"'"
|
||||
})
|
||||
|
||||
// send by default to:
|
||||
// exchange = exchangeName
|
||||
// routingKey = <exchange>.request
|
||||
val stringRpcClient = RPC.newStringRpcClient(connection, exchangeName)
|
||||
|
||||
val response = stringRpcClient.call("AMQP Rocks!")
|
||||
log.info("Got response: "+response)
|
||||
|
||||
stringRpcClient.callAsync("AMQP is dead easy") {
|
||||
case response => log.info("This is handled async: "+response)
|
||||
}
|
||||
}
|
||||
|
||||
def easyProtobufRpc = {
|
||||
|
||||
val connection = AMQP.newConnection()
|
||||
|
||||
val exchangeName = "easy.protobuf.rpc"
|
||||
|
||||
def protobufRequestHandler(request: AddressProtocol): AddressProtocol = {
|
||||
AddressProtocol.newBuilder.setHostname(request.getHostname.reverse).setPort(request.getPort).build
|
||||
}
|
||||
|
||||
RPC.newProtobufRpcServer(connection, exchangeName, protobufRequestHandler)
|
||||
|
||||
val protobufRpcClient = RPC.newProtobufRpcClient[AddressProtocol, AddressProtocol](connection, exchangeName)
|
||||
|
||||
val response = protobufRpcClient.call(AddressProtocol.newBuilder.setHostname("localhost").setPort(4321).build)
|
||||
|
||||
log.info("Got response: "+response)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -60,7 +60,6 @@ private[amqp] class FaultTolerantConnectionActor(connectionParameters: Connectio
|
|||
}
|
||||
|
||||
private def connect = if (connection.isEmpty || !connection.get.isOpen) {
|
||||
|
||||
try {
|
||||
connection = Some(connectionFactory.newConnection)
|
||||
connection.foreach {
|
||||
|
|
@ -108,7 +107,7 @@ private[amqp] class FaultTolerantConnectionActor(connectionParameters: Connectio
|
|||
override def shutdown = {
|
||||
reconnectionTimer.cancel
|
||||
// make sure shutdown is called on all linked actors so they can do channel cleanup before connection is killed
|
||||
self.linkedActorsAsList.foreach(_.stop)
|
||||
self.shutdownLinkedActors
|
||||
disconnect
|
||||
}
|
||||
|
||||
|
|
@ -118,5 +117,4 @@ private[amqp] class FaultTolerantConnectionActor(connectionParameters: Connectio
|
|||
notifyCallback(Reconnecting)
|
||||
connect
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -5,11 +5,14 @@
|
|||
package se.scalablesolutions.akka.amqp
|
||||
|
||||
import com.rabbitmq.client._
|
||||
|
||||
import se.scalablesolutions.akka.amqp.AMQP.ProducerParameters
|
||||
import se.scalablesolutions.akka.AkkaException
|
||||
|
||||
private[amqp] class ProducerActor(producerParameters: ProducerParameters)
|
||||
extends FaultTolerantChannelActor(producerParameters.exchangeParameters, producerParameters.channelParameters) {
|
||||
|
||||
extends FaultTolerantChannelActor(
|
||||
producerParameters.exchangeParameters, producerParameters.channelParameters) {
|
||||
|
||||
import producerParameters._
|
||||
import exchangeParameters._
|
||||
|
||||
|
|
@ -32,29 +35,29 @@ private[amqp] class ProducerActor(producerParameters: ProducerParameters)
|
|||
case Some(listener) => ch.setReturnListener(listener)
|
||||
case None => ch.setReturnListener(new ReturnListener() {
|
||||
def handleBasicReturn(
|
||||
replyCode: Int,
|
||||
replyText: String,
|
||||
exchange: String,
|
||||
routingKey: String,
|
||||
properties: com.rabbitmq.client.AMQP.BasicProperties,
|
||||
body: Array[Byte]) = {
|
||||
replyCode: Int,
|
||||
replyText: String,
|
||||
exchange: String,
|
||||
routingKey: String,
|
||||
properties: com.rabbitmq.client.AMQP.BasicProperties,
|
||||
body: Array[Byte]) = {
|
||||
throw new MessageNotDeliveredException(
|
||||
"Could not deliver message [" + body +
|
||||
"] with reply code [" + replyCode +
|
||||
"] with reply text [" + replyText +
|
||||
"] and routing key [" + routingKey +
|
||||
"] to exchange [" + exchange + "]",
|
||||
"] with reply code [" + replyCode +
|
||||
"] with reply text [" + replyText +
|
||||
"] and routing key [" + routingKey +
|
||||
"] to exchange [" + exchange + "]",
|
||||
replyCode, replyText, exchange, routingKey, properties, body)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override def toString(): String =
|
||||
override def toString =
|
||||
"AMQP.Poducer[id= "+ self.id +
|
||||
", exchange=" + exchangeName +
|
||||
", exchangeType=" + exchangeType +
|
||||
", durable=" + exchangeDurable +
|
||||
", autoDelete=" + exchangeAutoDelete + "]"
|
||||
", exchange=" + exchangeName +
|
||||
", exchangeType=" + exchangeType +
|
||||
", durable=" + exchangeDurable +
|
||||
", autoDelete=" + exchangeAutoDelete + "]"
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,182 @@
|
|||
package se.scalablesolutions.akka.amqp.rpc
|
||||
|
||||
import se.scalablesolutions.akka.amqp.AMQP._
|
||||
import com.google.protobuf.Message
|
||||
import se.scalablesolutions.akka.actor.{Actor, ActorRef}
|
||||
import Actor._
|
||||
import se.scalablesolutions.akka.amqp._
|
||||
|
||||
object RPC {
|
||||
|
||||
def newRpcClient[O, I](connection: ActorRef,
|
||||
exchangeParameters: ExchangeParameters,
|
||||
routingKey: String,
|
||||
serializer: RpcClientSerializer[O, I],
|
||||
channelParameters: Option[ChannelParameters] = None): ActorRef = {
|
||||
val rpcActor: ActorRef = actorOf(new RpcClientActor[O, I](
|
||||
exchangeParameters, routingKey, serializer, channelParameters))
|
||||
connection.startLink(rpcActor)
|
||||
rpcActor ! Start
|
||||
rpcActor
|
||||
}
|
||||
|
||||
def newRpcServer[I, O](connection: ActorRef,
|
||||
exchangeParameters: ExchangeParameters,
|
||||
routingKey: String,
|
||||
serializer: RpcServerSerializer[I, O],
|
||||
requestHandler: I => O,
|
||||
queueName: Option[String] = None,
|
||||
channelParameters: Option[ChannelParameters] = None): RpcServerHandle = {
|
||||
val producer = newProducer(connection, ProducerParameters(
|
||||
ExchangeParameters("", ExchangeType.Direct), channelParameters = channelParameters))
|
||||
val rpcServer = actorOf(new RpcServerActor[I, O](producer, serializer, requestHandler))
|
||||
val consumer = newConsumer(connection, ConsumerParameters(exchangeParameters, routingKey, rpcServer,
|
||||
channelParameters = channelParameters, selfAcknowledging = false, queueName = queueName))
|
||||
RpcServerHandle(producer, consumer)
|
||||
}
|
||||
|
||||
case class RpcServerHandle(producer: ActorRef, consumer: ActorRef) {
|
||||
def stop = {
|
||||
consumer.stop
|
||||
producer.stop
|
||||
}
|
||||
}
|
||||
|
||||
case class RpcClientSerializer[O, I](toBinary: ToBinary[O], fromBinary: FromBinary[I])
|
||||
|
||||
case class RpcServerSerializer[I, O](fromBinary: FromBinary[I], toBinary: ToBinary[O])
|
||||
|
||||
|
||||
/**
|
||||
* RPC convenience
|
||||
*/
|
||||
class RpcClient[O, I](client: ActorRef){
|
||||
def call(request: O, timeout: Long = 5000): Option[I] = {
|
||||
(client.!!(request, timeout)).as[I]
|
||||
}
|
||||
|
||||
def callAsync(request: O, timeout: Long = 5000)(responseHandler: PartialFunction[Option[I],Unit]) = {
|
||||
spawn {
|
||||
val result = call(request, timeout)
|
||||
responseHandler.apply(result)
|
||||
}
|
||||
}
|
||||
def stop = client.stop
|
||||
}
|
||||
|
||||
def newProtobufRpcServer[I <: Message, O <: Message](
|
||||
connection: ActorRef,
|
||||
exchange: String,
|
||||
requestHandler: I => O,
|
||||
routingKey: Option[String] = None,
|
||||
queueName: Option[String] = None,
|
||||
durable: Boolean = false,
|
||||
autoDelete: Boolean = true)(implicit manifest: Manifest[I]): RpcServerHandle = {
|
||||
|
||||
val serializer = new RpcServerSerializer[I, O](
|
||||
new FromBinary[I] {
|
||||
def fromBinary(bytes: Array[Byte]): I = {
|
||||
createProtobufFromBytes[I](bytes)
|
||||
}
|
||||
}, new ToBinary[O] {
|
||||
def toBinary(t: O) = t.toByteArray
|
||||
})
|
||||
|
||||
startServer(connection, exchange, requestHandler, routingKey, queueName, durable, autoDelete, serializer)
|
||||
}
|
||||
|
||||
def newProtobufRpcClient[O <: Message, I <: Message](
|
||||
connection: ActorRef,
|
||||
exchange: String,
|
||||
routingKey: Option[String] = None,
|
||||
durable: Boolean = false,
|
||||
autoDelete: Boolean = true,
|
||||
passive: Boolean = true)(implicit manifest: Manifest[I]): RpcClient[O, I] = {
|
||||
|
||||
|
||||
val serializer = new RpcClientSerializer[O, I](
|
||||
new ToBinary[O] {
|
||||
def toBinary(t: O) = t.toByteArray
|
||||
}, new FromBinary[I] {
|
||||
def fromBinary(bytes: Array[Byte]): I = {
|
||||
createProtobufFromBytes[I](bytes)
|
||||
}
|
||||
})
|
||||
|
||||
startClient(connection, exchange, routingKey, durable, autoDelete, passive, serializer)
|
||||
}
|
||||
|
||||
def newStringRpcServer(connection: ActorRef,
|
||||
exchange: String,
|
||||
requestHandler: String => String,
|
||||
routingKey: Option[String] = None,
|
||||
queueName: Option[String] = None,
|
||||
durable: Boolean = false,
|
||||
autoDelete: Boolean = true): RpcServerHandle = {
|
||||
|
||||
val serializer = new RpcServerSerializer[String, String](
|
||||
new FromBinary[String] {
|
||||
def fromBinary(bytes: Array[Byte]): String = {
|
||||
new String(bytes)
|
||||
}
|
||||
}, new ToBinary[String] {
|
||||
def toBinary(t: String) = t.getBytes
|
||||
})
|
||||
|
||||
startServer(connection, exchange, requestHandler, routingKey, queueName, durable, autoDelete, serializer)
|
||||
}
|
||||
|
||||
def newStringRpcClient(connection: ActorRef,
|
||||
exchange: String,
|
||||
routingKey: Option[String] = None,
|
||||
durable: Boolean = false,
|
||||
autoDelete: Boolean = true,
|
||||
passive: Boolean = true): RpcClient[String, String] = {
|
||||
|
||||
|
||||
val serializer = new RpcClientSerializer[String, String](
|
||||
new ToBinary[String] {
|
||||
def toBinary(t: String) = t.getBytes
|
||||
}, new FromBinary[String] {
|
||||
def fromBinary(bytes: Array[Byte]): String = {
|
||||
new String(bytes)
|
||||
}
|
||||
})
|
||||
|
||||
startClient(connection, exchange, routingKey, durable, autoDelete, passive, serializer)
|
||||
}
|
||||
|
||||
private def startClient[O, I](connection: ActorRef,
|
||||
exchange: String,
|
||||
routingKey: Option[String] = None,
|
||||
durable: Boolean = false,
|
||||
autoDelete: Boolean = true,
|
||||
passive: Boolean = true,
|
||||
serializer: RpcClientSerializer[O, I]): RpcClient[O, I] = {
|
||||
|
||||
val exchangeParameters = ExchangeParameters(exchange, ExchangeType.Topic,
|
||||
exchangeDurable = durable, exchangeAutoDelete = autoDelete, exchangePassive = passive)
|
||||
val rKey = routingKey.getOrElse("%s.request".format(exchange))
|
||||
|
||||
val client = newRpcClient(connection, exchangeParameters, rKey, serializer)
|
||||
new RpcClient(client)
|
||||
}
|
||||
|
||||
private def startServer[I, O](connection: ActorRef,
|
||||
exchange: String,
|
||||
requestHandler: I => O,
|
||||
routingKey: Option[String] = None,
|
||||
queueName: Option[String] = None,
|
||||
durable: Boolean = false,
|
||||
autoDelete: Boolean = true,
|
||||
serializer: RpcServerSerializer[I, O]): RpcServerHandle = {
|
||||
|
||||
val exchangeParameters = ExchangeParameters(exchange, ExchangeType.Topic,
|
||||
exchangeDurable = durable, exchangeAutoDelete = autoDelete)
|
||||
val rKey = routingKey.getOrElse("%s.request".format(exchange))
|
||||
val qName = queueName.getOrElse("%s.in".format(rKey))
|
||||
|
||||
newRpcServer(connection, exchangeParameters, rKey, serializer, requestHandler, queueName = Some(qName))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -4,16 +4,16 @@
|
|||
|
||||
package se.scalablesolutions.akka.amqp
|
||||
|
||||
import se.scalablesolutions.akka.serialization.Serializer
|
||||
import com.rabbitmq.client.{Channel, RpcClient}
|
||||
import rpc.RPC.RpcClientSerializer
|
||||
import se.scalablesolutions.akka.amqp.AMQP.{ChannelParameters, ExchangeParameters}
|
||||
|
||||
import com.rabbitmq.client.{Channel, RpcClient}
|
||||
import se.scalablesolutions.akka.amqp.AMQP.{RpcClientSerializer, ChannelParameters, ExchangeParameters}
|
||||
|
||||
class RpcClientActor[I,O](exchangeParameters: ExchangeParameters,
|
||||
routingKey: String,
|
||||
serializer: RpcClientSerializer[I,O],
|
||||
channelParameters: Option[ChannelParameters] = None) extends FaultTolerantChannelActor(exchangeParameters, channelParameters) {
|
||||
class RpcClientActor[I,O](
|
||||
exchangeParameters: ExchangeParameters,
|
||||
routingKey: String,
|
||||
serializer: RpcClientSerializer[I,O],
|
||||
channelParameters: Option[ChannelParameters] = None)
|
||||
extends FaultTolerantChannelActor(exchangeParameters, channelParameters) {
|
||||
|
||||
import exchangeParameters._
|
||||
|
||||
|
|
@ -39,5 +39,11 @@ class RpcClientActor[I,O](exchangeParameters: ExchangeParameters,
|
|||
super.preRestart(reason)
|
||||
}
|
||||
|
||||
|
||||
override def shutdown = {
|
||||
rpcClient.foreach(rpc => rpc.close)
|
||||
super.shutdown
|
||||
}
|
||||
|
||||
override def toString = "AMQP.RpcClient[exchange=" +exchangeName + ", routingKey=" + routingKey+ "]"
|
||||
}
|
||||
}
|
||||
|
|
@ -4,11 +4,14 @@
|
|||
|
||||
package se.scalablesolutions.akka.amqp
|
||||
|
||||
import rpc.RPC.RpcServerSerializer
|
||||
import se.scalablesolutions.akka.actor.{ActorRef, Actor}
|
||||
import com.rabbitmq.client.AMQP.BasicProperties
|
||||
import se.scalablesolutions.akka.amqp.AMQP.RpcServerSerializer
|
||||
|
||||
class RpcServerActor[I,O](producer: ActorRef, serializer: RpcServerSerializer[I,O], requestHandler: PartialFunction[I, O]) extends Actor {
|
||||
class RpcServerActor[I,O](
|
||||
producer: ActorRef,
|
||||
serializer: RpcServerSerializer[I,O],
|
||||
requestHandler: I => O) extends Actor {
|
||||
|
||||
log.info("%s started", this)
|
||||
|
||||
|
|
@ -29,6 +32,5 @@ class RpcServerActor[I,O](producer: ActorRef, serializer: RpcServerSerializer[I,
|
|||
case Acknowledged(tag) => log.debug("%s acknowledged delivery with tag %d", this, tag)
|
||||
}
|
||||
|
||||
override def toString(): String =
|
||||
"AMQP.RpcServer[]"
|
||||
}
|
||||
override def toString = "AMQP.RpcServer[]"
|
||||
}
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
||||
*/
|
||||
|
||||
package se.scalablesolutions.akka.amqp.test
|
||||
|
||||
import se.scalablesolutions.akka.util.Logging
|
||||
import org.scalatest.junit.JUnitSuite
|
||||
import org.junit.Test
|
||||
import se.scalablesolutions.akka.amqp._
|
||||
import se.scalablesolutions.akka.actor.Actor._
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
import java.util.concurrent.{CountDownLatch, TimeUnit}
|
||||
import se.scalablesolutions.akka.serialization.Serializer
|
||||
import se.scalablesolutions.akka.amqp.AMQP._
|
||||
|
||||
class AMQPRpcClientServerTest extends JUnitSuite with MustMatchers with Logging {
|
||||
|
||||
@Test
|
||||
def consumerMessage = if (AMQPTest.enabled) {
|
||||
val connection = AMQP.newConnection()
|
||||
try {
|
||||
|
||||
val countDown = new CountDownLatch(3)
|
||||
val channelCallback = actor {
|
||||
case Started => countDown.countDown
|
||||
case Restarting => ()
|
||||
case Stopped => ()
|
||||
}
|
||||
|
||||
val exchangeParameters = ExchangeParameters("text_topic_exchange", ExchangeType.Topic)
|
||||
val channelParameters = ChannelParameters(channelCallback
|
||||
= Some(channelCallback))
|
||||
|
||||
val serverFromBinary = new FromBinary[String] {
|
||||
def fromBinary(bytes: Array[Byte]) = new String(bytes)
|
||||
}
|
||||
val serverToBinary = new ToBinary[Int] {
|
||||
def toBinary(t: Int) = Array(t.toByte)
|
||||
}
|
||||
val rpcServerSerializer = new RpcServerSerializer[String, Int](serverFromBinary, serverToBinary)
|
||||
val rpcServer = AMQP.newRpcServer[String,Int](connection, exchangeParameters, "rpc.routing", rpcServerSerializer, {
|
||||
case "some_payload" => 3
|
||||
case _ => error("unknown request")
|
||||
}, channelParameters = Some(channelParameters))
|
||||
|
||||
val clientToBinary = new ToBinary[String] {
|
||||
def toBinary(t: String) = t.getBytes
|
||||
}
|
||||
val clientFromBinary = new FromBinary[Int] {
|
||||
def fromBinary(bytes: Array[Byte]) = bytes.head.toInt
|
||||
}
|
||||
val rpcClientSerializer = new RpcClientSerializer[String, Int](clientToBinary, clientFromBinary)
|
||||
val rpcClient = AMQP.newRpcClient[String,Int](connection, exchangeParameters, "rpc.routing", rpcClientSerializer,
|
||||
channelParameters = Some(channelParameters))
|
||||
|
||||
countDown.await(2, TimeUnit.SECONDS) must be (true)
|
||||
val response = rpcClient !! "some_payload"
|
||||
response must be (Some(3))
|
||||
} finally {
|
||||
connection.stop
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
def dummy {
|
||||
// amqp tests need local rabbitmq server running, so a disabled by default.
|
||||
// this dummy test makes sure that the whole test class doesn't fail because of missing tests
|
||||
assert(true)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,9 @@
|
|||
package se.scalablesolutions.akka.amqp.test
|
||||
|
||||
/**
|
||||
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
||||
*/
|
||||
|
||||
package se.scalablesolutions.akka.amqp.test
|
||||
|
||||
import se.scalablesolutions.akka.util.Logging
|
||||
import org.scalatest.junit.JUnitSuite
|
||||
import org.junit.Test
|
||||
import java.util.concurrent.TimeUnit
|
||||
import se.scalablesolutions.akka.actor.{Actor, ActorRef}
|
||||
import org.multiverse.api.latches.StandardLatch
|
||||
|
|
@ -14,11 +11,13 @@ import com.rabbitmq.client.ShutdownSignalException
|
|||
import se.scalablesolutions.akka.amqp._
|
||||
import se.scalablesolutions.akka.amqp.AMQP.ConnectionParameters
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
import org.scalatest.junit.JUnitSuite
|
||||
import org.junit.Test
|
||||
|
||||
class AMQPConnectionRecoveryTest extends JUnitSuite with MustMatchers with Logging {
|
||||
class AMQPConnectionRecoveryTest extends JUnitSuite with MustMatchers {
|
||||
|
||||
@Test
|
||||
def connectionAndRecovery = if (AMQPTest.enabled) {
|
||||
def connectionAndRecovery = if (AMQPTest.enabled) AMQPTest.withCleanEndState {
|
||||
|
||||
val connectedLatch = new StandardLatch
|
||||
val reconnectingLatch = new StandardLatch
|
||||
|
|
@ -45,15 +44,9 @@ class AMQPConnectionRecoveryTest extends JUnitSuite with MustMatchers with Loggi
|
|||
reconnectedLatch.tryAwait(2, TimeUnit.SECONDS) must be(true)
|
||||
|
||||
} finally {
|
||||
connection.stop
|
||||
AMQP.shutdownAll
|
||||
disconnectedLatch.tryAwait(2, TimeUnit.SECONDS) must be(true)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
def dummy {
|
||||
// amqp tests need local rabbitmq server running, so a disabled by default.
|
||||
// this dummy test makes sure that the whole test class doesn't fail because of missing tests
|
||||
assert(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,9 @@
|
|||
package se.scalablesolutions.akka.amqp.test
|
||||
|
||||
/**
|
||||
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
||||
*/
|
||||
|
||||
package se.scalablesolutions.akka.amqp.test
|
||||
|
||||
import se.scalablesolutions.akka.util.Logging
|
||||
import org.scalatest.junit.JUnitSuite
|
||||
import se.scalablesolutions.akka.actor.Actor._
|
||||
import org.multiverse.api.latches.StandardLatch
|
||||
import com.rabbitmq.client.ShutdownSignalException
|
||||
import se.scalablesolutions.akka.amqp._
|
||||
|
|
@ -15,11 +12,13 @@ import java.util.concurrent.TimeUnit
|
|||
import se.scalablesolutions.akka.actor.ActorRef
|
||||
import org.junit.Test
|
||||
import se.scalablesolutions.akka.amqp.AMQP._
|
||||
import org.scalatest.junit.JUnitSuite
|
||||
import se.scalablesolutions.akka.actor.Actor._
|
||||
|
||||
class AMQPConsumerChannelRecoveryTest extends JUnitSuite with MustMatchers with Logging {
|
||||
class AMQPConsumerChannelRecoveryTest extends JUnitSuite with MustMatchers {
|
||||
|
||||
@Test
|
||||
def consumerChannelRecovery = if (AMQPTest.enabled) {
|
||||
def consumerChannelRecovery = if (AMQPTest.enabled) AMQPTest.withCleanEndState {
|
||||
|
||||
val connection = AMQP.newConnection(ConnectionParameters(initReconnectDelay = 50))
|
||||
try {
|
||||
|
|
@ -60,11 +59,4 @@ class AMQPConsumerChannelRecoveryTest extends JUnitSuite with MustMatchers with
|
|||
connection.stop
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
def dummy {
|
||||
// amqp tests need local rabbitmq server running, so a disabled by default.
|
||||
// this dummy test makes sure that the whole test class doesn't fail because of missing tests
|
||||
assert(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +1,24 @@
|
|||
package se.scalablesolutions.akka.amqp.test
|
||||
|
||||
/**
|
||||
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
||||
*/
|
||||
|
||||
package se.scalablesolutions.akka.amqp.test
|
||||
|
||||
import se.scalablesolutions.akka.util.Logging
|
||||
import org.scalatest.junit.JUnitSuite
|
||||
import se.scalablesolutions.akka.actor.Actor._
|
||||
import org.multiverse.api.latches.StandardLatch
|
||||
import com.rabbitmq.client.ShutdownSignalException
|
||||
import se.scalablesolutions.akka.amqp._
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
import java.util.concurrent.TimeUnit
|
||||
import se.scalablesolutions.akka.actor.ActorRef
|
||||
import org.junit.Test
|
||||
import se.scalablesolutions.akka.amqp.AMQP._
|
||||
import org.scalatest.junit.JUnitSuite
|
||||
import se.scalablesolutions.akka.actor.{Actor, ActorRef}
|
||||
import Actor._
|
||||
|
||||
class AMQPConsumerConnectionRecoveryTest extends JUnitSuite with MustMatchers with Logging {
|
||||
class AMQPConsumerConnectionRecoveryTest extends JUnitSuite with MustMatchers {
|
||||
|
||||
@Test
|
||||
def consumerConnectionRecovery = if (AMQPTest.enabled) {
|
||||
def consumerConnectionRecovery = if (AMQPTest.enabled) AMQPTest.withCleanEndState {
|
||||
|
||||
val connection = AMQP.newConnection(ConnectionParameters(initReconnectDelay = 50))
|
||||
try {
|
||||
|
|
@ -79,11 +78,4 @@ class AMQPConsumerConnectionRecoveryTest extends JUnitSuite with MustMatchers wi
|
|||
connection.stop
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
def dummy {
|
||||
// amqp tests need local rabbitmq server running, so a disabled by default.
|
||||
// this dummy test makes sure that the whole test class doesn't fail because of missing tests
|
||||
assert(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,9 @@
|
|||
package se.scalablesolutions.akka.amqp.test
|
||||
|
||||
/**
|
||||
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
||||
*/
|
||||
|
||||
package se.scalablesolutions.akka.amqp.test
|
||||
|
||||
import se.scalablesolutions.akka.util.Logging
|
||||
import org.scalatest.junit.JUnitSuite
|
||||
import org.multiverse.api.latches.StandardLatch
|
||||
import se.scalablesolutions.akka.actor.Actor._
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
import se.scalablesolutions.akka.amqp._
|
||||
|
|
@ -14,11 +11,13 @@ import org.junit.Test
|
|||
import se.scalablesolutions.akka.actor.ActorRef
|
||||
import java.util.concurrent.{CountDownLatch, TimeUnit}
|
||||
import se.scalablesolutions.akka.amqp.AMQP.{ExchangeParameters, ConsumerParameters, ChannelParameters, ProducerParameters}
|
||||
import org.multiverse.api.latches.StandardLatch
|
||||
import org.scalatest.junit.JUnitSuite
|
||||
|
||||
class AMQPConsumerManualAcknowledgeTest extends JUnitSuite with MustMatchers with Logging {
|
||||
class AMQPConsumerManualAcknowledgeTest extends JUnitSuite with MustMatchers {
|
||||
|
||||
@Test
|
||||
def consumerMessageManualAcknowledge = if (AMQPTest.enabled) {
|
||||
def consumerMessageManualAcknowledge = if (AMQPTest.enabled) AMQPTest.withCleanEndState {
|
||||
val connection = AMQP.newConnection()
|
||||
try {
|
||||
val countDown = new CountDownLatch(2)
|
||||
|
|
@ -57,11 +56,4 @@ class AMQPConsumerManualAcknowledgeTest extends JUnitSuite with MustMatchers wit
|
|||
connection.stop
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
def dummy {
|
||||
// amqp tests need local rabbitmq server running, so a disabled by default.
|
||||
// this dummy test makes sure that the whole test class doesn't fail because of missing tests
|
||||
assert(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
package se.scalablesolutions.akka.amqp.test
|
||||
|
||||
/**
|
||||
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
||||
*/
|
||||
|
||||
import se.scalablesolutions.akka.actor.Actor._
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
import se.scalablesolutions.akka.amqp._
|
||||
import org.junit.Test
|
||||
import se.scalablesolutions.akka.actor.ActorRef
|
||||
import java.util.concurrent.{CountDownLatch, TimeUnit}
|
||||
import se.scalablesolutions.akka.amqp.AMQP.{ExchangeParameters, ConsumerParameters, ChannelParameters, ProducerParameters}
|
||||
import org.multiverse.api.latches.StandardLatch
|
||||
import org.scalatest.junit.JUnitSuite
|
||||
|
||||
class AMQPConsumerManualRejectTest extends JUnitSuite with MustMatchers {
|
||||
|
||||
@Test
|
||||
def consumerMessageManualAcknowledge = if (AMQPTest.enabled) AMQPTest.withCleanEndState {
|
||||
val connection = AMQP.newConnection()
|
||||
try {
|
||||
val countDown = new CountDownLatch(2)
|
||||
val restartingLatch = new StandardLatch
|
||||
val channelCallback = actor {
|
||||
case Started => countDown.countDown
|
||||
case Restarting => restartingLatch.open
|
||||
case Stopped => ()
|
||||
}
|
||||
val exchangeParameters = ExchangeParameters("text_exchange",ExchangeType.Direct)
|
||||
val channelParameters = ChannelParameters(channelCallback = Some(channelCallback))
|
||||
|
||||
val rejectedLatch = new StandardLatch
|
||||
val consumer:ActorRef = AMQP.newConsumer(connection, ConsumerParameters(exchangeParameters, "manual.reject.this", actor {
|
||||
case Delivery(payload, _, deliveryTag, _, sender) => {
|
||||
sender.foreach(_ ! Reject(deliveryTag))
|
||||
}
|
||||
case Rejected(deliveryTag) => rejectedLatch.open
|
||||
}, queueName = Some("self.reject.queue"), selfAcknowledging = false, queueAutoDelete = false, channelParameters = Some(channelParameters)))
|
||||
|
||||
val producer = AMQP.newProducer(connection,
|
||||
ProducerParameters(exchangeParameters, channelParameters = Some(channelParameters)))
|
||||
|
||||
countDown.await(2, TimeUnit.SECONDS) must be (true)
|
||||
producer ! Message("some_payload".getBytes, "manual.reject.this")
|
||||
|
||||
rejectedLatch.tryAwait(2, TimeUnit.SECONDS) must be (true)
|
||||
restartingLatch.tryAwait(2, TimeUnit.SECONDS) must be (true)
|
||||
} finally {
|
||||
connection.stop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +1,22 @@
|
|||
package se.scalablesolutions.akka.amqp.test
|
||||
|
||||
/**
|
||||
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
||||
*/
|
||||
|
||||
package se.scalablesolutions.akka.amqp.test
|
||||
|
||||
import se.scalablesolutions.akka.util.Logging
|
||||
import org.scalatest.junit.JUnitSuite
|
||||
import org.junit.Test
|
||||
import se.scalablesolutions.akka.amqp._
|
||||
import org.multiverse.api.latches.StandardLatch
|
||||
import se.scalablesolutions.akka.actor.Actor._
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
import java.util.concurrent.{CountDownLatch, TimeUnit}
|
||||
import se.scalablesolutions.akka.amqp.AMQP.{ExchangeParameters, ConsumerParameters, ChannelParameters, ProducerParameters}
|
||||
import org.scalatest.junit.JUnitSuite
|
||||
import org.junit.Test
|
||||
|
||||
class AMQPConsumerMessageTest extends JUnitSuite with MustMatchers with Logging {
|
||||
class AMQPConsumerMessageTest extends JUnitSuite with MustMatchers {
|
||||
|
||||
@Test
|
||||
def consumerMessage = if (AMQPTest.enabled) {
|
||||
def consumerMessage = if (AMQPTest.enabled) AMQPTest.withCleanEndState {
|
||||
val connection = AMQP.newConnection()
|
||||
try {
|
||||
|
||||
|
|
@ -38,7 +37,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)
|
||||
|
|
@ -46,11 +45,4 @@ class AMQPConsumerMessageTest extends JUnitSuite with MustMatchers with Logging
|
|||
connection.stop
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
def dummy {
|
||||
// amqp tests need local rabbitmq server running, so a disabled by default.
|
||||
// this dummy test makes sure that the whole test class doesn't fail because of missing tests
|
||||
assert(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,9 @@
|
|||
package se.scalablesolutions.akka.amqp.test
|
||||
|
||||
/**
|
||||
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
||||
*/
|
||||
|
||||
package se.scalablesolutions.akka.amqp.test
|
||||
|
||||
import se.scalablesolutions.akka.util.Logging
|
||||
import org.scalatest.junit.JUnitSuite
|
||||
import org.junit.Test
|
||||
import java.util.concurrent.TimeUnit
|
||||
import se.scalablesolutions.akka.actor.{Actor, ActorRef}
|
||||
import org.multiverse.api.latches.StandardLatch
|
||||
|
|
@ -14,11 +11,13 @@ import com.rabbitmq.client.ShutdownSignalException
|
|||
import se.scalablesolutions.akka.amqp._
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
import se.scalablesolutions.akka.amqp.AMQP.{ExchangeParameters, ChannelParameters, ProducerParameters, ConnectionParameters}
|
||||
import org.scalatest.junit.JUnitSuite
|
||||
import org.junit.Test
|
||||
|
||||
class AMQPProducerChannelRecoveryTest extends JUnitSuite with MustMatchers with Logging {
|
||||
class AMQPProducerChannelRecoveryTest extends JUnitSuite with MustMatchers {
|
||||
|
||||
@Test
|
||||
def producerChannelRecovery = if (AMQPTest.enabled) {
|
||||
def producerChannelRecovery = if (AMQPTest.enabled) AMQPTest.withCleanEndState {
|
||||
|
||||
val connection = AMQP.newConnection(ConnectionParameters(initReconnectDelay = 50))
|
||||
|
||||
|
|
@ -53,11 +52,4 @@ class AMQPProducerChannelRecoveryTest extends JUnitSuite with MustMatchers with
|
|||
connection.stop
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
def dummy {
|
||||
// amqp tests need local rabbitmq server running, so a disabled by default.
|
||||
// this dummy test makes sure that the whole test class doesn't fail because of missing tests
|
||||
assert(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,9 @@
|
|||
package se.scalablesolutions.akka.amqp.test
|
||||
|
||||
/**
|
||||
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
||||
*/
|
||||
|
||||
package se.scalablesolutions.akka.amqp.test
|
||||
|
||||
import se.scalablesolutions.akka.util.Logging
|
||||
import org.scalatest.junit.JUnitSuite
|
||||
import org.junit.Test
|
||||
import java.util.concurrent.TimeUnit
|
||||
import se.scalablesolutions.akka.actor.{Actor, ActorRef}
|
||||
import org.multiverse.api.latches.StandardLatch
|
||||
|
|
@ -14,11 +11,13 @@ import com.rabbitmq.client.ShutdownSignalException
|
|||
import se.scalablesolutions.akka.amqp._
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
import se.scalablesolutions.akka.amqp.AMQP.{ExchangeParameters, ChannelParameters, ProducerParameters, ConnectionParameters}
|
||||
import org.scalatest.junit.JUnitSuite
|
||||
import org.junit.Test
|
||||
|
||||
class AMQPProducerConnectionRecoveryTest extends JUnitSuite with MustMatchers with Logging {
|
||||
class AMQPProducerConnectionRecoveryTest extends JUnitSuite with MustMatchers {
|
||||
|
||||
@Test
|
||||
def producerConnectionRecovery = if (AMQPTest.enabled) {
|
||||
def producerConnectionRecovery = if (AMQPTest.enabled) AMQPTest.withCleanEndState {
|
||||
|
||||
val connection = AMQP.newConnection(ConnectionParameters(initReconnectDelay = 50))
|
||||
try {
|
||||
|
|
@ -52,11 +51,4 @@ class AMQPProducerConnectionRecoveryTest extends JUnitSuite with MustMatchers wi
|
|||
connection.stop
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
def dummy {
|
||||
// amqp tests need local rabbitmq server running, so a disabled by default.
|
||||
// this dummy test makes sure that the whole test class doesn't fail because of missing tests
|
||||
assert(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,9 @@
|
|||
package se.scalablesolutions.akka.amqp.test
|
||||
|
||||
/**
|
||||
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
||||
*/
|
||||
|
||||
package se.scalablesolutions.akka.amqp.test
|
||||
|
||||
import se.scalablesolutions.akka.util.Logging
|
||||
import org.scalatest.junit.JUnitSuite
|
||||
import org.junit.Test
|
||||
import java.util.concurrent.TimeUnit
|
||||
import se.scalablesolutions.akka.actor.ActorRef
|
||||
import org.multiverse.api.latches.StandardLatch
|
||||
|
|
@ -16,12 +13,14 @@ import com.rabbitmq.client.AMQP.BasicProperties
|
|||
import java.lang.String
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
import se.scalablesolutions.akka.amqp.AMQP.{ExchangeParameters, ProducerParameters}
|
||||
import org.scalatest.junit.JUnitSuite
|
||||
import org.junit.Test
|
||||
|
||||
class AMQPProducerMessageTest extends JUnitSuite with MustMatchers with Logging {
|
||||
class AMQPProducerMessageTest extends JUnitSuite with MustMatchers {
|
||||
|
||||
@Test
|
||||
def producerMessage = if (AMQPTest.enabled) {
|
||||
|
||||
def producerMessage = if (AMQPTest.enabled) AMQPTest.withCleanEndState {
|
||||
|
||||
val connection: ActorRef = AMQP.newConnection()
|
||||
try {
|
||||
val returnLatch = new StandardLatch
|
||||
|
|
@ -41,11 +40,4 @@ class AMQPProducerMessageTest extends JUnitSuite with MustMatchers with Logging
|
|||
connection.stop
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
def dummy {
|
||||
// amqp tests need local rabbitmq server running, so a disabled by default.
|
||||
// this dummy test makes sure that the whole test class doesn't fail because of missing tests
|
||||
assert(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package se.scalablesolutions.akka.amqp.test
|
||||
|
||||
/**
|
||||
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
||||
*/
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
import org.scalatest.junit.JUnitSuite
|
||||
import se.scalablesolutions.akka.amqp.AMQP
|
||||
import org.junit.Test
|
||||
import org.multiverse.api.latches.StandardLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
import se.scalablesolutions.akka.amqp.rpc.RPC
|
||||
import se.scalablesolutions.akka.remote.protocol.RemoteProtocol.AddressProtocol
|
||||
|
||||
class AMQPProtobufProducerConsumerTest extends JUnitSuite with MustMatchers {
|
||||
|
||||
@Test
|
||||
def consumerMessage = if (AMQPTest.enabled) AMQPTest.withCleanEndState {
|
||||
|
||||
val connection = AMQP.newConnection()
|
||||
|
||||
val responseLatch = new StandardLatch
|
||||
|
||||
RPC.newProtobufRpcServer(connection, "protoexchange", requestHandler)
|
||||
|
||||
val request = AddressProtocol.newBuilder.setHostname("testhost").setPort(4321).build
|
||||
|
||||
def responseHandler(response: AddressProtocol) = {
|
||||
assert(response.getHostname == request.getHostname.reverse)
|
||||
responseLatch.open
|
||||
}
|
||||
AMQP.newProtobufConsumer(connection, "", responseHandler, Some("proto.reply.key"))
|
||||
|
||||
val producer = AMQP.newProtobufProducer[AddressProtocol](connection, "protoexchange")
|
||||
producer.send(request, Some("proto.reply.key"))
|
||||
|
||||
responseLatch.tryAwait(2, TimeUnit.SECONDS) must be (true)
|
||||
}
|
||||
|
||||
def requestHandler(request: AddressProtocol): AddressProtocol = {
|
||||
AddressProtocol.newBuilder.setHostname(request.getHostname.reverse).setPort(request.getPort).build
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
package se.scalablesolutions.akka.amqp.test
|
||||
|
||||
/**
|
||||
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
||||
*/
|
||||
|
||||
import se.scalablesolutions.akka.amqp._
|
||||
import rpc.RPC
|
||||
import rpc.RPC.{RpcClientSerializer, RpcServerSerializer}
|
||||
import se.scalablesolutions.akka.actor.Actor._
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
import java.util.concurrent.{CountDownLatch, TimeUnit}
|
||||
import se.scalablesolutions.akka.amqp.AMQP._
|
||||
import org.scalatest.junit.JUnitSuite
|
||||
import org.junit.Test
|
||||
|
||||
class AMQPRpcClientServerTest extends JUnitSuite with MustMatchers {
|
||||
|
||||
@Test
|
||||
def consumerMessage = if (AMQPTest.enabled) AMQPTest.withCleanEndState {
|
||||
|
||||
val connection = AMQP.newConnection()
|
||||
|
||||
val countDown = new CountDownLatch(3)
|
||||
val channelCallback = actor {
|
||||
case Started => countDown.countDown
|
||||
case Restarting => ()
|
||||
case Stopped => ()
|
||||
}
|
||||
|
||||
val exchangeParameters = ExchangeParameters("text_topic_exchange", ExchangeType.Topic)
|
||||
val channelParameters = ChannelParameters(channelCallback
|
||||
= Some(channelCallback))
|
||||
|
||||
val rpcServerSerializer = new RpcServerSerializer[String, Int](
|
||||
new FromBinary[String] {
|
||||
def fromBinary(bytes: Array[Byte]) = new String(bytes)
|
||||
}, new ToBinary[Int] {
|
||||
def toBinary(t: Int) = Array(t.toByte)
|
||||
})
|
||||
|
||||
def requestHandler(request: String) = 3
|
||||
|
||||
val rpcServer = RPC.newRpcServer[String, Int](connection, exchangeParameters, "rpc.routing", rpcServerSerializer,
|
||||
requestHandler, channelParameters = Some(channelParameters))
|
||||
|
||||
val rpcClientSerializer = new RpcClientSerializer[String, Int](
|
||||
new ToBinary[String] {
|
||||
def toBinary(t: String) = t.getBytes
|
||||
}, new FromBinary[Int] {
|
||||
def fromBinary(bytes: Array[Byte]) = bytes.head.toInt
|
||||
})
|
||||
|
||||
val rpcClient = RPC.newRpcClient[String, Int](connection, exchangeParameters, "rpc.routing", rpcClientSerializer,
|
||||
channelParameters = Some(channelParameters))
|
||||
|
||||
countDown.await(2, TimeUnit.SECONDS) must be(true)
|
||||
val response = rpcClient !! "some_payload"
|
||||
response must be(Some(3))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package se.scalablesolutions.akka.amqp.test
|
||||
|
||||
/**
|
||||
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
||||
*/
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
import org.scalatest.junit.JUnitSuite
|
||||
import se.scalablesolutions.akka.amqp.AMQP
|
||||
import se.scalablesolutions.akka.remote.protocol.RemoteProtocol.AddressProtocol
|
||||
import org.junit.Test
|
||||
import se.scalablesolutions.akka.amqp.rpc.RPC
|
||||
import org.multiverse.api.latches.StandardLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class AMQPRpcProtobufTest extends JUnitSuite with MustMatchers {
|
||||
|
||||
@Test
|
||||
def consumerMessage = if (AMQPTest.enabled) AMQPTest.withCleanEndState {
|
||||
|
||||
val connection = AMQP.newConnection()
|
||||
|
||||
RPC.newProtobufRpcServer(connection, "protoservice", requestHandler)
|
||||
|
||||
val protobufClient = RPC.newProtobufRpcClient[AddressProtocol, AddressProtocol](connection, "protoservice")
|
||||
|
||||
val request = AddressProtocol.newBuilder.setHostname("testhost").setPort(4321).build
|
||||
|
||||
protobufClient.call(request) match {
|
||||
case Some(response) => assert(response.getHostname == request.getHostname.reverse)
|
||||
case None => fail("no response")
|
||||
}
|
||||
|
||||
val aSyncLatch = new StandardLatch
|
||||
protobufClient.callAsync(request) {
|
||||
case Some(response) => {
|
||||
assert(response.getHostname == request.getHostname.reverse)
|
||||
aSyncLatch.open
|
||||
}
|
||||
case None => fail("no response")
|
||||
}
|
||||
|
||||
aSyncLatch.tryAwait(2, TimeUnit.SECONDS) must be (true)
|
||||
|
||||
}
|
||||
|
||||
def requestHandler(request: AddressProtocol): AddressProtocol = {
|
||||
AddressProtocol.newBuilder.setHostname(request.getHostname.reverse).setPort(request.getPort).build
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package se.scalablesolutions.akka.amqp.test
|
||||
|
||||
/**
|
||||
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
||||
*/
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
import org.scalatest.junit.JUnitSuite
|
||||
import se.scalablesolutions.akka.amqp.AMQP
|
||||
import org.junit.Test
|
||||
import se.scalablesolutions.akka.amqp.rpc.RPC
|
||||
import org.multiverse.api.latches.StandardLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class AMQPRpcStringTest extends JUnitSuite with MustMatchers {
|
||||
|
||||
@Test
|
||||
def consumerMessage = if (AMQPTest.enabled) AMQPTest.withCleanEndState {
|
||||
|
||||
val connection = AMQP.newConnection()
|
||||
|
||||
RPC.newStringRpcServer(connection, "stringservice", requestHandler)
|
||||
|
||||
val protobufClient = RPC.newStringRpcClient(connection, "stringservice")
|
||||
|
||||
val request = "teststring"
|
||||
|
||||
protobufClient.call(request) match {
|
||||
case Some(response) => assert(response == request.reverse)
|
||||
case None => fail("no response")
|
||||
}
|
||||
|
||||
val aSyncLatch = new StandardLatch
|
||||
protobufClient.callAsync(request) {
|
||||
case Some(response) => {
|
||||
assert(response == request.reverse)
|
||||
aSyncLatch.open
|
||||
}
|
||||
case None => fail("no response")
|
||||
}
|
||||
|
||||
aSyncLatch.tryAwait(2, TimeUnit.SECONDS) must be (true)
|
||||
}
|
||||
|
||||
def requestHandler(request: String): String= {
|
||||
request.reverse
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package se.scalablesolutions.akka.amqp.test
|
||||
|
||||
/**
|
||||
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
||||
*/
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
import org.scalatest.junit.JUnitSuite
|
||||
import se.scalablesolutions.akka.amqp.AMQP
|
||||
import org.junit.Test
|
||||
import org.multiverse.api.latches.StandardLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
import se.scalablesolutions.akka.amqp.rpc.RPC
|
||||
|
||||
class AMQPStringProducerConsumerTest extends JUnitSuite with MustMatchers {
|
||||
|
||||
@Test
|
||||
def consumerMessage = if (AMQPTest.enabled) AMQPTest.withCleanEndState {
|
||||
|
||||
val connection = AMQP.newConnection()
|
||||
|
||||
val responseLatch = new StandardLatch
|
||||
|
||||
RPC.newStringRpcServer(connection, "stringexchange", requestHandler)
|
||||
|
||||
val request = "somemessage"
|
||||
|
||||
def responseHandler(response: String) = {
|
||||
|
||||
assert(response == request.reverse)
|
||||
responseLatch.open
|
||||
}
|
||||
AMQP.newStringConsumer(connection, "", responseHandler, Some("string.reply.key"))
|
||||
|
||||
val producer = AMQP.newStringProducer(connection, "stringexchange")
|
||||
producer.send(request, Some("string.reply.key"))
|
||||
|
||||
responseLatch.tryAwait(2, TimeUnit.SECONDS) must be (true)
|
||||
}
|
||||
|
||||
def requestHandler(request: String): String= {
|
||||
println("###### Reverse")
|
||||
request.reverse
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,16 @@
|
|||
|
||||
package se.scalablesolutions.akka.amqp.test
|
||||
|
||||
import se.scalablesolutions.akka.amqp.AMQP
|
||||
object AMQPTest {
|
||||
|
||||
def enabled = false
|
||||
}
|
||||
|
||||
def withCleanEndState(action: => Unit) {
|
||||
try {
|
||||
action
|
||||
} finally {
|
||||
AMQP.shutdownAll
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,17 +2,26 @@
|
|||
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
||||
*/
|
||||
|
||||
package se.scalablesolutions.akka.actor.annotation;
|
||||
package se.scalablesolutions.akka.camel;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Annotation used by implementations of {@link se.scalablesolutions.akka.actor.TypedActor}
|
||||
* (on method-level) to define consumer endpoints.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD})
|
||||
public @interface consume {
|
||||
|
||||
/**
|
||||
* Consumer endpoint URI
|
||||
*/
|
||||
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,24 +1,24 @@
|
|||
/**
|
||||
* 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>.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
trait CamelService extends Bootable with Logging {
|
||||
|
||||
import CamelContextManager._
|
||||
|
||||
private[camel] val consumerPublisher = actorOf[ConsumerPublisher]
|
||||
private[camel] val publishRequestor = actorOf[PublishRequestor]
|
||||
|
||||
|
|
@ -32,72 +32,142 @@ 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 = {
|
||||
super.onLoad
|
||||
|
||||
// Only init and start if not already done by application
|
||||
if (!initialized) init
|
||||
if (!started) start
|
||||
if (!CamelContextManager.initialized) CamelContextManager.init
|
||||
if (!CamelContextManager.started) CamelContextManager.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
|
||||
publishRequestor ! PublishRequestorInit(consumerPublisher)
|
||||
|
||||
// Register this instance as current CamelService
|
||||
CamelServiceManager.register(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the CamelService.
|
||||
*/
|
||||
abstract override def onUnload = {
|
||||
// Unregister this instance as current CamelService
|
||||
CamelServiceManager.unregister(this)
|
||||
|
||||
// Remove related listeners from registry
|
||||
ActorRegistry.removeListener(publishRequestor)
|
||||
AspectInitRegistry.removeListener(publishRequestor)
|
||||
|
||||
// Stop related services
|
||||
consumerPublisher.stop
|
||||
stop
|
||||
CamelContextManager.stop
|
||||
|
||||
super.onUnload
|
||||
}
|
||||
|
||||
@deprecated("use start() instead")
|
||||
def load: CamelService = {
|
||||
onLoad
|
||||
this
|
||||
}
|
||||
|
||||
@deprecated("use stop() instead")
|
||||
def unload = onUnload
|
||||
|
||||
/**
|
||||
* Starts the CamelService.
|
||||
*
|
||||
* @see onLoad
|
||||
*/
|
||||
def load: CamelService = {
|
||||
def start: CamelService = {
|
||||
onLoad
|
||||
this
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Stops the CamelService.
|
||||
*
|
||||
* @see onUnload
|
||||
*/
|
||||
def unload = onUnload
|
||||
def stop = 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.
|
||||
* ...
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
object CamelService {
|
||||
object CamelServiceManager {
|
||||
|
||||
/**
|
||||
* Creates a new CamelService instance.
|
||||
* The current (optional) CamelService. Is defined when a CamelService has been started.
|
||||
*/
|
||||
def newInstance: CamelService = new DefaultCamelService
|
||||
private var _current: Option[CamelService] = None
|
||||
|
||||
/**
|
||||
* Starts a new CamelService and makes it the current CamelService.
|
||||
*/
|
||||
def startCamelService = CamelServiceFactory.createCamelService.start
|
||||
|
||||
/**
|
||||
* Stops the current CamelService.
|
||||
*/
|
||||
def stopCamelService = service.stop
|
||||
|
||||
/**
|
||||
* Returns the current CamelService.
|
||||
*
|
||||
* @throws IllegalStateException if there's no current CamelService.
|
||||
*/
|
||||
def service =
|
||||
if (_current.isDefined) _current.get
|
||||
else throw new IllegalStateException("no current CamelService")
|
||||
|
||||
private[camel] def register(service: CamelService) =
|
||||
if (_current.isDefined) throw new IllegalStateException("current CamelService already registered")
|
||||
else _current = Some(service)
|
||||
|
||||
private[camel] def unregister(service: CamelService) =
|
||||
if (_current == Some(service)) _current = None
|
||||
else throw new IllegalStateException("only current CamelService can be unregistered")
|
||||
}
|
||||
|
||||
/**
|
||||
* Default CamelService implementation to be created in Java applications with
|
||||
* <pre>
|
||||
* CamelService service = new DefaultCamelService()
|
||||
* </pre>
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
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,9 @@
|
|||
|
||||
package se.scalablesolutions.akka.camel
|
||||
|
||||
import se.scalablesolutions.akka.actor.{ActorRef, Actor}
|
||||
import se.scalablesolutions.akka.actor._
|
||||
|
||||
import java.net.InetSocketAddress
|
||||
|
||||
/**
|
||||
* Mixed in by Actor implementations that consume message from Camel endpoints.
|
||||
|
|
@ -25,6 +27,58 @@ trait Consumer { self: Actor =>
|
|||
def blocking = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Java-friendly {@link Consumer} inherited by
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link UntypedConsumerActor}</li>
|
||||
* <li>{@link RemoteUntypedConsumerActor}</li>
|
||||
* <li>{@link UntypedConsumerTransactor}</li>
|
||||
* </ul>
|
||||
*
|
||||
* implementations.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
trait UntypedConsumer extends Consumer { self: UntypedActor =>
|
||||
|
||||
final override def endpointUri = getEndpointUri
|
||||
|
||||
final override def blocking = isBlocking
|
||||
|
||||
/**
|
||||
* Returns the Camel endpoint URI to consume messages from.
|
||||
*/
|
||||
def getEndpointUri(): 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 isBlocking() = super.blocking
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclass this abstract class to create an MDB-style untyped consumer actor. This
|
||||
* class is meant to be used from Java.
|
||||
*/
|
||||
abstract class UntypedConsumerActor extends UntypedActor with UntypedConsumer
|
||||
|
||||
/**
|
||||
* Subclass this abstract class to create an MDB-style transacted untyped consumer
|
||||
* actor. This class is meant to be used from Java.
|
||||
*/
|
||||
abstract class UntypedConsumerTransactor extends UntypedTransactor with UntypedConsumer
|
||||
|
||||
/**
|
||||
* Subclass this abstract class to create an MDB-style remote untyped consumer
|
||||
* actor. This class is meant to be used from Java.
|
||||
*/
|
||||
abstract class RemoteUntypedConsumerActor(address: InetSocketAddress) extends RemoteUntypedActor(address) with UntypedConsumer {
|
||||
def this(host: String, port: Int) = this(new InetSocketAddress(host, port))
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
|
|
@ -42,4 +96,4 @@ private[camel] object Consumer {
|
|||
else if (actorRef.remoteAddress.isDefined) None
|
||||
else Some(f(actorRef.actor.asInstanceOf[Consumer]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,8 +12,7 @@ import java.util.concurrent.CountDownLatch
|
|||
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
|
||||
|
||||
/**
|
||||
|
|
@ -37,15 +36,15 @@ private[camel] object ConsumerPublisher extends Logging {
|
|||
}
|
||||
|
||||
/**
|
||||
* 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 +54,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
|
||||
*/
|
||||
|
|
@ -149,20 +148,20 @@ private[camel] class ConsumerActorRoute(endpointUri: String, uuid: String, block
|
|||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
|
@ -209,7 +208,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)
|
||||
|
||||
|
|
@ -244,32 +243,30 @@ private[camel] case class ConsumerRegistered(actorRef: ActorRef, uri: String, uu
|
|||
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
|
||||
* <code>@consume</code> annotated POJO method a separate instance of this class is
|
||||
* created.
|
||||
* Event indicating that an typed actor proxy has been created for a typed actor. For each <code>@consume</code>
|
||||
* annotated typed actor 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
|
||||
* <code>@consume</code> annotated POJO method a separate instance of this class is
|
||||
* created.
|
||||
* Event indicating that an typed actor has been stopped. For each <code>@consume</code>
|
||||
* annotated typed object 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
|
||||
|
|
@ -306,18 +303,22 @@ 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] = {
|
||||
// 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])))
|
||||
yield f(m)
|
||||
def forConsumer[T](typedActor: AnyRef, init: AspectInit)(f: Method => T): List[T] = {
|
||||
if (init.remoteAddress.isDefined) Nil // let remote node publish typed actor methods on endpoints
|
||||
else {
|
||||
// TODO: support consumer annotation inheritance
|
||||
// - visit overridden methods in superclasses
|
||||
// - visit implemented method declarations in interfaces
|
||||
val intfClass = typedActor.getClass
|
||||
val implClass = init.targetInstance.getClass
|
||||
(for (m <- intfClass.getMethods.toList; if (m.isAnnotationPresent(classOf[consume]))) yield f(m)) ++
|
||||
(for (m <- implClass.getMethods.toList; if (m.isAnnotationPresent(classOf[consume]))) yield f(m))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -326,13 +327,13 @@ 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(activeObject, init) {
|
||||
m => ConsumerMethodRegistered(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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -342,13 +343,13 @@ private[camel] object ConsumerMethodRegistered {
|
|||
*/
|
||||
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
|
||||
* 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 forConsumer(activeObject: AnyRef, init: AspectInit): List[ConsumerMethodUnregistered] = {
|
||||
ConsumerMethod.forConsumer(activeObject, init) {
|
||||
m => ConsumerMethodUnregistered(activeObject, init, m.getAnnotation(classOf[consume]).value, m)
|
||||
def forConsumer(typedActor: AnyRef, init: AspectInit): List[ConsumerMethodUnregistered] = {
|
||||
ConsumerMethod.forConsumer(typedActor, init) {
|
||||
m => ConsumerMethodUnregistered(typedActor, init, m.getAnnotation(classOf[consume]).value, m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ case class Message(val body: Any, val headers: Map[String, Any] = Map.empty) {
|
|||
*
|
||||
* @see CamelContextManager.
|
||||
*/
|
||||
@deprecated("use bodyAs[T](implicit m: Manifest[T]): T instead")
|
||||
def bodyAs[T](clazz: Class[T]): T =
|
||||
CamelContextManager.context.getTypeConverter.mandatoryConvertTo[T](clazz, body)
|
||||
|
||||
|
|
@ -43,6 +42,26 @@ case class Message(val body: Any, val headers: Map[String, Any] = Map.empty) {
|
|||
*/
|
||||
def headers(names: Set[String]): Map[String, Any] = headers.filter(names contains _._1)
|
||||
|
||||
/**
|
||||
* Returns the header with given <code>name</code>. Throws <code>NoSuchElementException</code>
|
||||
* if the header doesn't exist.
|
||||
*/
|
||||
def header(name: String): Any = headers(name)
|
||||
|
||||
/**
|
||||
* Returns the header with given <code>name</code> converted to type <code>T</code>. Throws
|
||||
* <code>NoSuchElementException</code> if the header doesn't exist.
|
||||
*/
|
||||
def headerAs[T](name: String)(implicit m: Manifest[T]): T =
|
||||
CamelContextManager.context.getTypeConverter.mandatoryConvertTo[T](m.erasure.asInstanceOf[Class[T]], header(name))
|
||||
|
||||
/**
|
||||
* Returns the header with given <code>name</code> converted to type given by the <code>clazz</code>
|
||||
* argument. Throws <code>NoSuchElementException</code> if the header doesn't exist.
|
||||
*/
|
||||
def headerAs[T](name: String, clazz: Class[T]): T =
|
||||
CamelContextManager.context.getTypeConverter.mandatoryConvertTo[T](clazz, header(name))
|
||||
|
||||
/**
|
||||
* Creates a Message with a new <code>body</code> using a <code>transformer</code> function.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -9,14 +9,14 @@ import CamelMessageConversion.toExchangeAdapter
|
|||
import org.apache.camel._
|
||||
import org.apache.camel.processor.SendProcessor
|
||||
|
||||
import se.scalablesolutions.akka.actor.{Actor, ActorRef}
|
||||
import se.scalablesolutions.akka.actor.{Actor, ActorRef, UntypedActor}
|
||||
|
||||
/**
|
||||
* Mixed in by Actor implementations that produce messages to Camel endpoints.
|
||||
* Support trait for producing messages to Camel endpoints.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
trait Producer { this: Actor =>
|
||||
trait ProducerSupport { this: Actor =>
|
||||
|
||||
/**
|
||||
* Message headers to copy by default from request message to response-message.
|
||||
|
|
@ -141,11 +141,6 @@ trait Producer { this: Actor =>
|
|||
case msg => if (!oneway) self.reply(msg)
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation of Actor.receive
|
||||
*/
|
||||
protected def receive = produce
|
||||
|
||||
/**
|
||||
* Creates a new Exchange with given <code>pattern</code> from the endpoint specified by
|
||||
* <code>endpointUri</code>.
|
||||
|
|
@ -162,6 +157,78 @@ trait Producer { this: Actor =>
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mixed in by Actor implementations that produce messages to Camel endpoints.
|
||||
*/
|
||||
trait Producer extends ProducerSupport { this: Actor =>
|
||||
|
||||
/**
|
||||
* Default implementation of Actor.receive
|
||||
*/
|
||||
protected def receive = produce
|
||||
}
|
||||
|
||||
/**
|
||||
* Java-friendly {@link ProducerSupport} inherited by {@link UntypedProducerActor} implementations.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
trait UntypedProducer extends ProducerSupport { this: UntypedActor =>
|
||||
|
||||
final override def endpointUri = getEndpointUri
|
||||
|
||||
final override def oneway = isOneway
|
||||
|
||||
final override def receiveBeforeProduce = {
|
||||
case msg => onReceiveBeforeProduce(msg)
|
||||
}
|
||||
|
||||
final override def receiveAfterProduce = {
|
||||
case msg => onReceiveAfterProduce(msg)
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation of UntypedActor.onReceive
|
||||
*/
|
||||
def onReceive(message: Any) = produce(message)
|
||||
|
||||
/**
|
||||
* Returns the Camel endpoint URI to produce messages to.
|
||||
*/
|
||||
def getEndpointUri(): String
|
||||
|
||||
/**
|
||||
* If set to false (default), this producer expects a response message from the Camel endpoint.
|
||||
* If set to true, this producer communicates with the Camel endpoint with an in-only message
|
||||
* exchange pattern (fire and forget).
|
||||
*/
|
||||
def isOneway() = super.oneway
|
||||
|
||||
/**
|
||||
* Called before the message is sent to the endpoint specified by <code>getEndpointUri</code>. The original
|
||||
* message is passed as argument. By default, this method simply returns the argument but may be overridden
|
||||
* by subclasses.
|
||||
*/
|
||||
@throws(classOf[Exception])
|
||||
def onReceiveBeforeProduce(message: Any): Any = super.receiveBeforeProduce(message)
|
||||
|
||||
/**
|
||||
* Called after the a result was received from the endpoint specified by <code>getEndpointUri</code>. The
|
||||
* result is passed as argument. By default, this method replies the result back to the original sender
|
||||
* if <code>isOneway</code> returns false. If <code>isOneway</code> returns true then nothing is done. This
|
||||
* method may be overridden by subclasses.
|
||||
*/
|
||||
@throws(classOf[Exception])
|
||||
def onReceiveAfterProduce(message: Any): Unit = super.receiveAfterProduce(message)
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclass this abstract class to create an untyped producer actor. This class is meant to be used from Java.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
abstract class UntypedProducerActor extends UntypedActor with UntypedProducer
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -14,18 +14,17 @@ 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 CamelMessageConversion.toExchangeAdapter
|
||||
import se.scalablesolutions.akka.dispatch.{CompletableFuture, MessageInvocation, MessageDispatcher}
|
||||
import se.scalablesolutions.akka.stm.TransactionConfig
|
||||
import se.scalablesolutions.akka.actor.{ScalaActorRef, ActorRegistry, Actor, ActorRef}
|
||||
import se.scalablesolutions.akka.AkkaException
|
||||
|
||||
import scala.reflect.BeanProperty
|
||||
|
||||
import CamelMessageConversion.toExchangeAdapter
|
||||
import java.lang.Throwable
|
||||
|
||||
/**
|
||||
* Camel component for sending messages to and receiving replies from actors.
|
||||
* Camel component for sending messages to and receiving replies from (untyped) actors.
|
||||
*
|
||||
* @see se.scalablesolutions.akka.camel.component.ActorEndpoint
|
||||
* @see se.scalablesolutions.akka.camel.component.ActorProducer
|
||||
|
|
@ -50,7 +49,7 @@ class ActorComponent extends DefaultComponent {
|
|||
}
|
||||
|
||||
/**
|
||||
* Camel endpoint for referencing an actor. The actor reference is given by the endpoint URI.
|
||||
* Camel endpoint for referencing an (untyped) actor. The actor reference is given by the endpoint URI.
|
||||
* 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>,
|
||||
|
|
@ -68,7 +67,7 @@ class ActorEndpoint(uri: String,
|
|||
val uuid: Option[String]) extends DefaultEndpoint(uri, comp) {
|
||||
|
||||
/**
|
||||
* Blocking of client thread during two-way message exchanges with consumer actors. This is set
|
||||
* Blocking of caller 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
|
||||
|
|
@ -91,7 +90,7 @@ class ActorEndpoint(uri: String,
|
|||
}
|
||||
|
||||
/**
|
||||
* Sends the in-message of an exchange to an actor. If the exchange pattern is out-capable and
|
||||
* Sends the in-message of an exchange to an (untyped) 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.
|
||||
*
|
||||
|
|
@ -132,10 +131,8 @@ class ActorProducer(val ep: ActorEndpoint) extends DefaultProducer(ep) with Asyn
|
|||
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))
|
||||
}
|
||||
case None => throw new TimeoutException("timeout (%d ms) while waiting response from %s"
|
||||
format (actor.timeout, ep.getEndpointUri))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -150,9 +147,8 @@ class ActorProducer(val ep: ActorEndpoint) extends DefaultProducer(ep) with Asyn
|
|||
else targetByUuid(ep.uuid.get)
|
||||
|
||||
private def targetById(id: String) = ActorRegistry.actorsFor(id) match {
|
||||
case Nil => None
|
||||
case actor :: Nil => Some(actor)
|
||||
case actors => Some(actors.head)
|
||||
case actors if actors.length == 0 => None
|
||||
case actors => Some(actors(0))
|
||||
}
|
||||
|
||||
private def targetByUuid(uuid: String) = ActorRegistry.actorFor(uuid)
|
||||
|
|
@ -200,7 +196,7 @@ private[akka] object AsyncCallbackAdapter {
|
|||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
private[akka] class AsyncCallbackAdapter(exchange: Exchange, callback: AsyncCallback) extends ActorRef {
|
||||
private[akka] class AsyncCallbackAdapter(exchange: Exchange, callback: AsyncCallback) extends ActorRef with ScalaActorRef {
|
||||
|
||||
def start = {
|
||||
_isRunning = true
|
||||
|
|
@ -242,15 +238,15 @@ private[akka] class AsyncCallbackAdapter(exchange: Exchange, callback: AsyncCall
|
|||
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 spawn(clazz: Class[_ <: Actor]): ActorRef = unsupported
|
||||
def spawnRemote(clazz: Class[_ <: Actor], hostname: String, port: Int): ActorRef = unsupported
|
||||
def spawnLink(clazz: Class[_ <: Actor]): ActorRef = unsupported
|
||||
def spawnLinkRemote(clazz: Class[_ <: Actor], hostname: String, port: Int): ActorRef = unsupported
|
||||
def shutdownLinkedActors: Unit = unsupported
|
||||
def mailboxSize: Int = 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: Deque[MessageInvocation] = 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
|
||||
|
|
@ -260,7 +256,7 @@ private[akka] class AsyncCallbackAdapter(exchange: Exchange, callback: AsyncCall
|
|||
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
|
||||
protected[akka] 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 +0,0 @@
|
|||
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 class PojoBase {
|
||||
|
||||
public String m1(String b, String h) {
|
||||
return "m1base: " + b + " " + h;
|
||||
}
|
||||
|
||||
@consume("direct:m2base")
|
||||
public String m2(@Body String b, @Header("test") String h) {
|
||||
return "m2base: " + b + " " + h;
|
||||
}
|
||||
|
||||
@consume("direct:m3base")
|
||||
public String m3(@Body String b, @Header("test") String h) {
|
||||
return "m3base: " + b + " " + h;
|
||||
}
|
||||
|
||||
@consume("direct:m4base")
|
||||
public String m4(@Body String b, @Header("test") String h) {
|
||||
return "m4base: " + b + " " + h;
|
||||
}
|
||||
|
||||
public void m5(@Body String b, @Header("test") String h) {
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
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 class PojoImpl 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) {
|
||||
return "m2impl: " + b + " " + h;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
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 PojoIntf {
|
||||
|
||||
public String m1(String b, String h);
|
||||
|
||||
@consume("direct:m2intf")
|
||||
public String m2(@Body String b, @Header("test") String h);
|
||||
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
package se.scalablesolutions.akka.camel;
|
||||
|
||||
import se.scalablesolutions.akka.actor.annotation.consume;
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
public class PojoRemote {
|
||||
|
||||
@consume("direct:remote-active-object")
|
||||
public String foo(String s) {
|
||||
return String.format("remote active object: %s", s);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
package se.scalablesolutions.akka.camel;
|
||||
|
||||
import se.scalablesolutions.akka.actor.annotation.consume;
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
public class PojoSingle {
|
||||
|
||||
@consume("direct:foo")
|
||||
public void foo(String b) {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
package se.scalablesolutions.akka.camel;
|
||||
|
||||
import org.apache.camel.Body;
|
||||
import org.apache.camel.Header;
|
||||
|
||||
import se.scalablesolutions.akka.actor.annotation.consume;
|
||||
|
||||
public class PojoSub extends PojoBase {
|
||||
|
||||
@Override
|
||||
@consume("direct:m1sub")
|
||||
public String m1(@Body String b, @Header("test") String h) {
|
||||
return "m1sub: " + b + " " + h;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String m2(String b, String h) {
|
||||
return "m2sub: " + b + " " + h;
|
||||
}
|
||||
|
||||
@Override
|
||||
@consume("direct:m3sub")
|
||||
public String m3(@Body String b, @Header("test") String h) {
|
||||
return "m3sub: " + b + " " + h;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package se.scalablesolutions.akka.camel;
|
||||
|
||||
import se.scalablesolutions.akka.camel.consume;
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
public interface SampleRemoteTypedConsumer {
|
||||
|
||||
@consume("direct:remote-typed-consumer")
|
||||
public String foo(String s);
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package se.scalablesolutions.akka.camel;
|
||||
|
||||
import se.scalablesolutions.akka.actor.TypedActor;
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
public class SampleRemoteTypedConsumerImpl extends TypedActor implements SampleRemoteTypedConsumer {
|
||||
|
||||
public String foo(String s) {
|
||||
return String.format("remote typed actor: %s", s);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package se.scalablesolutions.akka.camel;
|
||||
|
||||
import se.scalablesolutions.akka.camel.RemoteUntypedConsumerActor;
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
public class SampleRemoteUntypedConsumer extends RemoteUntypedConsumerActor {
|
||||
|
||||
public SampleRemoteUntypedConsumer() {
|
||||
this("localhost", 7774);
|
||||
}
|
||||
|
||||
public SampleRemoteUntypedConsumer(String host, int port) {
|
||||
super(host, port);
|
||||
}
|
||||
|
||||
public String getEndpointUri() {
|
||||
return "direct:remote-untyped-consumer";
|
||||
}
|
||||
|
||||
public void onReceive(Object message) {
|
||||
Message msg = (Message)message;
|
||||
String body = msg.bodyAs(String.class);
|
||||
String header = msg.headerAs("test", String.class);
|
||||
getContext().replySafe(String.format("%s %s", body, header));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package se.scalablesolutions.akka.camel;
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
public interface SampleTypedActor {
|
||||
|
||||
public String foo(String s);
|
||||
}
|
||||
|
|
@ -1,14 +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 Pojo {
|
||||
public class SampleTypedActorImpl extends TypedActor implements SampleTypedActor {
|
||||
|
||||
public String foo(String s) {
|
||||
return String.format("foo: %s", s);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package se.scalablesolutions.akka.camel;
|
||||
|
||||
import org.apache.camel.Body;
|
||||
import org.apache.camel.Header;
|
||||
|
||||
import se.scalablesolutions.akka.camel.consume;
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
public interface SampleTypedConsumer {
|
||||
|
||||
public String m1(String b, String h);
|
||||
public String m2(@Body String b, @Header("test") String h);
|
||||
public String m3(@Body String b, @Header("test") String h);
|
||||
|
||||
@consume("direct:m4")
|
||||
public String m4(@Body String b, @Header("test") String h);
|
||||
public void m5(@Body String b, @Header("test") String h);
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package se.scalablesolutions.akka.camel;
|
||||
|
||||
import se.scalablesolutions.akka.actor.TypedActor;
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
public class SampleTypedConsumerImpl extends TypedActor implements SampleTypedConsumer {
|
||||
|
||||
public String m1(String b, String h) {
|
||||
return "m1: " + b + " " + h;
|
||||
}
|
||||
|
||||
@consume("direct:m2")
|
||||
public String m2(String b, String h) {
|
||||
return "m2: " + b + " " + h;
|
||||
}
|
||||
|
||||
@consume("direct:m3")
|
||||
public String m3(String b, String h) {
|
||||
return "m3: " + b + " " + h;
|
||||
}
|
||||
|
||||
public String m4(String b, String h) {
|
||||
return "m4: " + b + " " + h;
|
||||
}
|
||||
|
||||
public void m5(String b, String h) {
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package se.scalablesolutions.akka.camel;
|
||||
|
||||
import se.scalablesolutions.akka.camel.consume;
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
public interface SampleTypedSingleConsumer {
|
||||
|
||||
@consume("direct:foo")
|
||||
public void foo(String b);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package se.scalablesolutions.akka.camel;
|
||||
|
||||
import se.scalablesolutions.akka.actor.TypedActor;
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
public class SampleTypedSingleConsumerImpl extends TypedActor implements SampleTypedSingleConsumer {
|
||||
|
||||
public void foo(String b) {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package se.scalablesolutions.akka.camel;
|
||||
|
||||
import se.scalablesolutions.akka.actor.UntypedActor;
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
public class SampleUntypedActor extends UntypedActor {
|
||||
public void onReceive(Object message) {
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package se.scalablesolutions.akka.camel;
|
||||
|
||||
import se.scalablesolutions.akka.camel.UntypedConsumerActor;
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
public class SampleUntypedConsumer extends UntypedConsumerActor {
|
||||
|
||||
public String getEndpointUri() {
|
||||
return "direct:test-untyped-consumer";
|
||||
}
|
||||
|
||||
public void onReceive(Object message) {
|
||||
Message msg = (Message)message;
|
||||
String body = msg.bodyAs(String.class);
|
||||
String header = msg.headerAs("test", String.class);
|
||||
getContext().replySafe(String.format("%s %s", body, header));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package se.scalablesolutions.akka.camel;
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
public class SampleUntypedConsumerBlocking extends UntypedConsumerActor {
|
||||
|
||||
public String getEndpointUri() {
|
||||
return "direct:test-untyped-consumer-blocking";
|
||||
}
|
||||
|
||||
public boolean isBlocking() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void onReceive(Object message) {
|
||||
Message msg = (Message)message;
|
||||
String body = msg.bodyAs(String.class);
|
||||
String header = msg.headerAs("test", String.class);
|
||||
getContext().replySafe(String.format("%s %s", body, header));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package se.scalablesolutions.akka.camel;
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
public class SampleUntypedForwardingProducer extends UntypedProducerActor {
|
||||
|
||||
public String getEndpointUri() {
|
||||
return "direct:producer-test-1";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceiveAfterProduce(Object message) {
|
||||
Message msg = (Message)message;
|
||||
String body = msg.bodyAs(String.class);
|
||||
CamelContextManager.template().sendBody("direct:forward-test-1", body);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package se.scalablesolutions.akka.camel;
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
public class SampleUntypedReplyingProducer extends UntypedProducerActor {
|
||||
|
||||
public String getEndpointUri() {
|
||||
return "direct:producer-test-1";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,203 +0,0 @@
|
|||
package se.scalablesolutions.akka.camel
|
||||
|
||||
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}
|
||||
|
||||
class CamelServiceFeatureTest extends FeatureSpec with BeforeAndAfterAll with GivenWhenThen {
|
||||
import CamelServiceFeatureTest._
|
||||
|
||||
var service: CamelService = _
|
||||
|
||||
override protected def beforeAll = {
|
||||
ActorRegistry.shutdownAll
|
||||
// create new CamelService instance
|
||||
service = CamelService.newInstance
|
||||
// register test consumer before starting the CamelService
|
||||
actorOf(new TestConsumer("direct:publish-test-1")).start
|
||||
// Configure a custom camel route
|
||||
CamelContextManager.init
|
||||
CamelContextManager.context.addRoutes(new TestRoute)
|
||||
// start consumer publisher, otherwise we cannot set message
|
||||
// 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
|
||||
// start the CamelService
|
||||
service.load
|
||||
// await publication of first test consumer
|
||||
assert(latch.await(5000, TimeUnit.MILLISECONDS))
|
||||
}
|
||||
|
||||
override protected def afterAll = {
|
||||
service.unload
|
||||
ActorRegistry.shutdownAll
|
||||
}
|
||||
|
||||
feature("Publish registered consumer actors in the global CamelContext") {
|
||||
|
||||
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
|
||||
actorOf(new TestConsumer("direct:publish-test-2")).start
|
||||
assert(latch.await(5000, TimeUnit.MILLISECONDS))
|
||||
|
||||
when("requests are sent to these actors")
|
||||
val response1 = CamelContextManager.template.requestBody("direct:publish-test-1", "msg1")
|
||||
val response2 = CamelContextManager.template.requestBody("direct:publish-test-2", "msg2")
|
||||
|
||||
then("both actors should have replied with expected responses")
|
||||
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.consumerPublisher !! SetExpectedMessageCount(1)).as[CountDownLatch].get
|
||||
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") {
|
||||
|
||||
scenario("access to unregistered consumer actor via Camel direct-endpoint fails") {
|
||||
val endpointUri = "direct:unpublish-test-1"
|
||||
|
||||
given("a consumer actor registered after CamelService startup")
|
||||
assert(CamelContextManager.context.hasEndpoint(endpointUri) eq null)
|
||||
var latch = (service.consumerPublisher !! SetExpectedMessageCount(1)).as[CountDownLatch].get
|
||||
val consumer = actorOf(new TestConsumer(endpointUri)).start
|
||||
assert(latch.await(5000, TimeUnit.MILLISECONDS))
|
||||
assert(CamelContextManager.context.hasEndpoint(endpointUri) ne null)
|
||||
|
||||
when("the actor is stopped")
|
||||
latch = (service.consumerPublisher !! SetExpectedMessageCount(1)).as[CountDownLatch].get
|
||||
consumer.stop
|
||||
assert(latch.await(5000, TimeUnit.MILLISECONDS))
|
||||
|
||||
then("the associated endpoint isn't accessible any more")
|
||||
intercept[CamelExecutionException] {
|
||||
CamelContextManager.template.requestBody(endpointUri, "msg1")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
feature("Configure a custom Camel route for the global CamelContext") {
|
||||
|
||||
scenario("access an actor from the custom Camel route") {
|
||||
|
||||
given("a registered actor and a custom route to that actor")
|
||||
val actor = actorOf[TestActor].start
|
||||
|
||||
when("sending a a message to that route")
|
||||
val response = CamelContextManager.template.requestBody("direct:custom-route-test-1", "msg3")
|
||||
|
||||
then("an expected response generated by the actor should be returned")
|
||||
assert(response === "received msg3")
|
||||
}
|
||||
}
|
||||
|
||||
feature("Publish active object methods in the global CamelContext") {
|
||||
|
||||
scenario("access active object 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])
|
||||
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("each should have returned a different response")
|
||||
assert(response1 === "m2base: x y")
|
||||
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)
|
||||
assert(latch.await(5000, TimeUnit.MILLISECONDS))
|
||||
}
|
||||
}
|
||||
|
||||
feature("Unpublish active object method from the global CamelContext") {
|
||||
|
||||
scenario("access to unregistered active object methof via Camel direct-endpoint fails") {
|
||||
|
||||
given("an active object registered after CamelService startup")
|
||||
var latch = (service.consumerPublisher !! SetExpectedMessageCount(3)).as[CountDownLatch].get
|
||||
val obj = ActiveObject.newInstance(classOf[PojoBase])
|
||||
assert(latch.await(5000, TimeUnit.MILLISECONDS))
|
||||
|
||||
when("the active object is stopped")
|
||||
latch = (service.consumerPublisher !! SetExpectedMessageCount(3)).as[CountDownLatch].get
|
||||
ActiveObject.stop(obj)
|
||||
assert(latch.await(5000, TimeUnit.MILLISECONDS))
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object CamelServiceFeatureTest {
|
||||
|
||||
class TestConsumer(uri: String) extends Actor with Consumer {
|
||||
def endpointUri = uri
|
||||
protected def receive = {
|
||||
case msg: Message => self.reply("received %s" format msg.body)
|
||||
}
|
||||
}
|
||||
|
||||
class TestBlocker(uri: String) extends Actor with Consumer {
|
||||
self.timeout = 1
|
||||
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 = {
|
||||
case msg: Message => self.reply("received %s" format msg.body)
|
||||
}
|
||||
}
|
||||
|
||||
class TestRoute extends RouteBuilder {
|
||||
def configure {
|
||||
from("direct:custom-route-test-1").to("actor:custom-actor-id")
|
||||
}
|
||||
}
|
||||
}
|
||||
63
akka-camel/src/test/scala/CamelServiceManagerSpec.scala
Normal file
63
akka-camel/src/test/scala/CamelServiceManagerSpec.scala
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
package se.scalablesolutions.akka.camel
|
||||
|
||||
import org.scalatest.{BeforeAndAfterAll, WordSpec}
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
|
||||
import se.scalablesolutions.akka.actor.ActorRegistry
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
class CamelServiceManagerSpec extends WordSpec with BeforeAndAfterAll with MustMatchers {
|
||||
|
||||
override def afterAll = ActorRegistry.shutdownAll
|
||||
|
||||
"A CamelServiceManager" when {
|
||||
"the startCamelService method been has been called" must {
|
||||
"have registered the started CamelService instance" in {
|
||||
val service = CamelServiceManager.startCamelService
|
||||
CamelServiceManager.service must be theSameInstanceAs (service)
|
||||
}
|
||||
}
|
||||
"the stopCamelService method been has been called" must {
|
||||
"have unregistered the current CamelService instance" in {
|
||||
val service = CamelServiceManager.stopCamelService
|
||||
intercept[IllegalStateException] { CamelServiceManager.service }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"A CamelServiceManager" when {
|
||||
val service = CamelServiceFactory.createCamelService
|
||||
"a CamelService instance has been started externally" must {
|
||||
"have registered the started CamelService instance" in {
|
||||
service.start
|
||||
CamelServiceManager.service must be theSameInstanceAs (service)
|
||||
}
|
||||
}
|
||||
"the current CamelService instance has been stopped externally" must {
|
||||
"have unregistered the current CamelService instance" in {
|
||||
service.stop
|
||||
intercept[IllegalStateException] { CamelServiceManager.service }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"A CamelServiceManager" when {
|
||||
"a CamelService has been started" must {
|
||||
"not allow further CamelService instances to be started" in {
|
||||
CamelServiceManager.startCamelService
|
||||
intercept[IllegalStateException] { CamelServiceManager.startCamelService }
|
||||
}
|
||||
}
|
||||
"a CamelService has been stopped" must {
|
||||
"only allow the current CamelService instance to be stopped" in {
|
||||
intercept[IllegalStateException] { CamelServiceFactory.createCamelService.stop }
|
||||
}
|
||||
"ensure that the current CamelService instance has been actually started" in {
|
||||
CamelServiceManager.stopCamelService
|
||||
intercept[IllegalStateException] { CamelServiceManager.stopCamelService }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
package se.scalablesolutions.akka.camel
|
||||
|
||||
import java.net.InetSocketAddress
|
||||
|
||||
import org.scalatest.junit.JUnitSuite
|
||||
|
||||
import se.scalablesolutions.akka.actor.{AspectInit, ActiveObject}
|
||||
import se.scalablesolutions.akka.camel.ConsumerMethodRegistered._
|
||||
import org.junit.{AfterClass, Test}
|
||||
|
||||
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 ascendingMethodName = (r1: ConsumerMethodRegistered, r2: ConsumerMethodRegistered) =>
|
||||
r1.method.getName < r2.method.getName
|
||||
|
||||
@Test def shouldSelectPojoBaseMethods234 = {
|
||||
val registered = forConsumer(activePojoBase, localAspectInit).sortWith(ascendingMethodName)
|
||||
assert(registered.size === 3)
|
||||
assert(registered.map(_.method.getName) === List("m2", "m3", "m4"))
|
||||
}
|
||||
|
||||
@Test def shouldSelectPojoSubMethods134 = {
|
||||
val registered = forConsumer(activePojoSub, localAspectInit).sortWith(ascendingMethodName)
|
||||
assert(registered.size === 3)
|
||||
assert(registered.map(_.method.getName) === List("m1", "m3", "m4"))
|
||||
}
|
||||
|
||||
@Test def shouldSelectPojoIntfMethod2 = {
|
||||
val registered = forConsumer(activePojoIntf, localAspectInit)
|
||||
assert(registered.size === 1)
|
||||
assert(registered(0).method.getName === "m2")
|
||||
}
|
||||
|
||||
@Test def shouldIgnoreRemoteProxies = {
|
||||
val registered = forConsumer(activePojoBase, remoteAspectInit)
|
||||
assert(registered.size === 0)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object ConsumerMethodRegisteredTest {
|
||||
val activePojoBase = ActiveObject.newInstance(classOf[PojoBase])
|
||||
val activePojoSub = ActiveObject.newInstance(classOf[PojoSub])
|
||||
val activePojoIntf = ActiveObject.newInstance(classOf[PojoIntf], new PojoImpl)
|
||||
|
||||
@AfterClass
|
||||
def afterClass = {
|
||||
ActiveObject.stop(activePojoBase)
|
||||
ActiveObject.stop(activePojoSub)
|
||||
ActiveObject.stop(activePojoIntf)
|
||||
}
|
||||
}
|
||||
|
|
@ -3,8 +3,46 @@ package se.scalablesolutions.akka.camel
|
|||
import org.junit.Test
|
||||
import org.scalatest.junit.JUnitSuite
|
||||
|
||||
import se.scalablesolutions.akka.actor.Actor
|
||||
import se.scalablesolutions.akka.actor.Actor._
|
||||
import se.scalablesolutions.akka.actor.{Actor, UntypedActor}
|
||||
|
||||
class ConsumerRegisteredTest extends JUnitSuite {
|
||||
import ConsumerRegisteredTest._
|
||||
|
||||
@Test def shouldCreateSomeNonBlockingPublishRequestFromConsumer = {
|
||||
val c = Actor.actorOf[ConsumerActor1]
|
||||
val event = ConsumerRegistered.forConsumer(c)
|
||||
assert(event === Some(ConsumerRegistered(c, "mock:test1", c.uuid, false)))
|
||||
}
|
||||
|
||||
@Test def shouldCreateSomeBlockingPublishRequestFromConsumer = {
|
||||
val c = Actor.actorOf[ConsumerActor2]
|
||||
val event = ConsumerRegistered.forConsumer(c)
|
||||
assert(event === Some(ConsumerRegistered(c, "mock:test2", c.uuid, true)))
|
||||
}
|
||||
|
||||
@Test def shouldCreateNoneFromConsumer = {
|
||||
val event = ConsumerRegistered.forConsumer(Actor.actorOf[PlainActor])
|
||||
assert(event === None)
|
||||
}
|
||||
|
||||
@Test def shouldCreateSomeNonBlockingPublishRequestFromUntypedConsumer = {
|
||||
val uc = UntypedActor.actorOf(classOf[SampleUntypedConsumer])
|
||||
val event = ConsumerRegistered.forConsumer(uc)
|
||||
assert(event === Some(ConsumerRegistered(uc, "direct:test-untyped-consumer", uc.uuid, false)))
|
||||
}
|
||||
|
||||
@Test def shouldCreateSomeBlockingPublishRequestFromUntypedConsumer = {
|
||||
val uc = UntypedActor.actorOf(classOf[SampleUntypedConsumerBlocking])
|
||||
val event = ConsumerRegistered.forConsumer(uc)
|
||||
assert(event === Some(ConsumerRegistered(uc, "direct:test-untyped-consumer-blocking", uc.uuid, true)))
|
||||
}
|
||||
|
||||
@Test def shouldCreateNoneFromUntypedConsumer = {
|
||||
val a = UntypedActor.actorOf(classOf[SampleUntypedActor])
|
||||
val event = ConsumerRegistered.forConsumer(a)
|
||||
assert(event === None)
|
||||
}
|
||||
}
|
||||
|
||||
object ConsumerRegisteredTest {
|
||||
class ConsumerActor1 extends Actor with Consumer {
|
||||
|
|
@ -22,24 +60,3 @@ object ConsumerRegisteredTest {
|
|||
protected def receive = null
|
||||
}
|
||||
}
|
||||
|
||||
class ConsumerRegisteredTest extends JUnitSuite {
|
||||
import ConsumerRegisteredTest._
|
||||
|
||||
@Test def shouldCreateSomeNonBlockingPublishRequest = {
|
||||
val ca = actorOf[ConsumerActor1]
|
||||
val event = ConsumerRegistered.forConsumer(ca)
|
||||
assert(event === Some(ConsumerRegistered(ca, "mock:test1", ca.uuid, false)))
|
||||
}
|
||||
|
||||
@Test def shouldCreateSomeBlockingPublishRequest = {
|
||||
val ca = actorOf[ConsumerActor2]
|
||||
val event = ConsumerRegistered.forConsumer(ca)
|
||||
assert(event === Some(ConsumerRegistered(ca, "mock:test2", ca.uuid, true)))
|
||||
}
|
||||
|
||||
@Test def shouldCreateNone = {
|
||||
val event = ConsumerRegistered.forConsumer(actorOf[PlainActor])
|
||||
assert(event === None)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
205
akka-camel/src/test/scala/ConsumerSpec.scala
Normal file
205
akka-camel/src/test/scala/ConsumerSpec.scala
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
package se.scalablesolutions.akka.camel
|
||||
|
||||
import java.util.concurrent.{TimeoutException, CountDownLatch, TimeUnit}
|
||||
|
||||
import org.apache.camel.CamelExecutionException
|
||||
import org.apache.camel.builder.RouteBuilder
|
||||
import org.scalatest.{BeforeAndAfterAll, WordSpec}
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
|
||||
import se.scalablesolutions.akka.actor.Actor._
|
||||
import se.scalablesolutions.akka.actor._
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
class ConsumerSpec extends WordSpec with BeforeAndAfterAll with MustMatchers {
|
||||
import CamelContextManager.template
|
||||
import ConsumerSpec._
|
||||
|
||||
var service: CamelService = _
|
||||
|
||||
override protected def beforeAll = {
|
||||
ActorRegistry.shutdownAll
|
||||
// create new CamelService instance
|
||||
service = CamelServiceFactory.createCamelService
|
||||
// register test consumer before starting the CamelService
|
||||
actorOf(new TestConsumer("direct:publish-test-1")).start
|
||||
// start consumer publisher, otherwise we cannot set message
|
||||
// count expectations in the next step (needed for testing only).
|
||||
service.consumerPublisher.start
|
||||
// set expectations on publish count
|
||||
val latch = service.expectEndpointActivationCount(1)
|
||||
// start the CamelService
|
||||
service.start
|
||||
// await publication of first test consumer
|
||||
latch.await(5000, TimeUnit.MILLISECONDS) must be (true)
|
||||
}
|
||||
|
||||
override protected def afterAll = {
|
||||
service.stop
|
||||
ActorRegistry.shutdownAll
|
||||
}
|
||||
|
||||
"A responding consumer" when {
|
||||
val consumer = actorOf(new TestConsumer("direct:publish-test-2"))
|
||||
"started before starting the CamelService" must {
|
||||
"support an in-out message exchange via its endpoint" in {
|
||||
template.requestBody("direct:publish-test-1", "msg1") must equal ("received msg1")
|
||||
}
|
||||
}
|
||||
"not started" must {
|
||||
"not have an associated endpoint in the CamelContext" in {
|
||||
CamelContextManager.context.hasEndpoint("direct:publish-test-2") must be (null)
|
||||
}
|
||||
}
|
||||
"started" must {
|
||||
"support an in-out message exchange via its endpoint" in {
|
||||
val latch = service.expectEndpointActivationCount(1)
|
||||
consumer.start
|
||||
latch.await(5000, TimeUnit.MILLISECONDS) must be (true)
|
||||
template.requestBody("direct:publish-test-2", "msg2") must equal ("received msg2")
|
||||
}
|
||||
"have an associated endpoint in the CamelContext" in {
|
||||
CamelContextManager.context.hasEndpoint("direct:publish-test-2") must not be (null)
|
||||
}
|
||||
}
|
||||
"stopped" must {
|
||||
"not support an in-out message exchange via its endpoint" in {
|
||||
val latch = service.expectEndpointDeactivationCount(1)
|
||||
consumer.stop
|
||||
latch.await(5000, TimeUnit.MILLISECONDS) must be (true)
|
||||
intercept[CamelExecutionException] {
|
||||
template.requestBody("direct:publish-test-2", "msg2")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"A responding, typed consumer" when {
|
||||
var actor: SampleTypedConsumer = null
|
||||
"started" must {
|
||||
"support in-out message exchanges via its endpoints" in {
|
||||
val latch = service.expectEndpointActivationCount(3)
|
||||
actor = TypedActor.newInstance(classOf[SampleTypedConsumer], classOf[SampleTypedConsumerImpl])
|
||||
latch.await(5000, TimeUnit.MILLISECONDS) must be (true)
|
||||
template.requestBodyAndHeader("direct:m2", "x", "test", "y") must equal ("m2: x y")
|
||||
template.requestBodyAndHeader("direct:m3", "x", "test", "y") must equal ("m3: x y")
|
||||
template.requestBodyAndHeader("direct:m4", "x", "test", "y") must equal ("m4: x y")
|
||||
}
|
||||
}
|
||||
"stopped" must {
|
||||
"not support in-out message exchanges via its endpoints" in {
|
||||
val latch = service.expectEndpointDeactivationCount(3)
|
||||
TypedActor.stop(actor)
|
||||
latch.await(5000, TimeUnit.MILLISECONDS) must be (true)
|
||||
intercept[CamelExecutionException] {
|
||||
template.requestBodyAndHeader("direct:m2", "x", "test", "y")
|
||||
}
|
||||
intercept[CamelExecutionException] {
|
||||
template.requestBodyAndHeader("direct:m3", "x", "test", "y")
|
||||
}
|
||||
intercept[CamelExecutionException] {
|
||||
template.requestBodyAndHeader("direct:m4", "x", "test", "y")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"A responding, typed consumer (Scala)" when {
|
||||
var actor: TestTypedConsumer = null
|
||||
"started" must {
|
||||
"support in-out message exchanges via its endpoints" in {
|
||||
val latch = service.expectEndpointActivationCount(2)
|
||||
actor = TypedActor.newInstance(classOf[TestTypedConsumer], classOf[TestTypedConsumerImpl])
|
||||
latch.await(5000, TimeUnit.MILLISECONDS) must be (true)
|
||||
template.requestBody("direct:publish-test-3", "x") must equal ("foo: x")
|
||||
template.requestBody("direct:publish-test-4", "x") must equal ("bar: x")
|
||||
}
|
||||
}
|
||||
"stopped" must {
|
||||
"not support in-out message exchanges via its endpoints" in {
|
||||
val latch = service.expectEndpointDeactivationCount(2)
|
||||
TypedActor.stop(actor)
|
||||
latch.await(5000, TimeUnit.MILLISECONDS) must be (true)
|
||||
intercept[CamelExecutionException] {
|
||||
template.requestBody("direct:publish-test-3", "x")
|
||||
}
|
||||
intercept[CamelExecutionException] {
|
||||
template.requestBody("direct:publish-test-4", "x")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"A responding, untyped consumer" when {
|
||||
val consumer = UntypedActor.actorOf(classOf[SampleUntypedConsumer])
|
||||
"started" must {
|
||||
"support an in-out message exchange via its endpoint" in {
|
||||
val latch = service.expectEndpointActivationCount(1)
|
||||
consumer.start
|
||||
latch.await(5000, TimeUnit.MILLISECONDS) must be (true)
|
||||
template.requestBodyAndHeader("direct:test-untyped-consumer", "x", "test", "y") must equal ("x y")
|
||||
}
|
||||
}
|
||||
"stopped" must {
|
||||
"not support an in-out message exchange via its endpoint" in {
|
||||
val latch = service.expectEndpointDeactivationCount(1)
|
||||
consumer.stop
|
||||
latch.await(5000, TimeUnit.MILLISECONDS) must be (true)
|
||||
intercept[CamelExecutionException] {
|
||||
template.sendBodyAndHeader("direct:test-untyped-consumer", "blah", "test", "blub")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"A non-responding, blocking consumer" when {
|
||||
"receiving an in-out message exchange" must {
|
||||
"lead to a TimeoutException" in {
|
||||
val latch = service.expectEndpointActivationCount(1)
|
||||
actorOf(new TestBlocker("direct:publish-test-5")).start
|
||||
latch.await(5000, TimeUnit.MILLISECONDS) must be (true)
|
||||
|
||||
try {
|
||||
template.requestBody("direct:publish-test-5", "msg3")
|
||||
fail("expected TimoutException not thrown")
|
||||
} catch {
|
||||
case e => {
|
||||
assert(e.getCause.isInstanceOf[TimeoutException])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object ConsumerSpec {
|
||||
class TestConsumer(uri: String) extends Actor with Consumer {
|
||||
def endpointUri = uri
|
||||
protected def receive = {
|
||||
case msg: Message => self.reply("received %s" format msg.body)
|
||||
}
|
||||
}
|
||||
|
||||
trait TestTypedConsumer {
|
||||
@consume("direct:publish-test-3")
|
||||
def foo(s: String): String
|
||||
def bar(s: String): String
|
||||
}
|
||||
|
||||
class TestTypedConsumerImpl extends TypedActor with TestTypedConsumer {
|
||||
def foo(s: String) = "foo: %s" format s
|
||||
@consume("direct:publish-test-4")
|
||||
def bar(s: String) = "bar: %s" format s
|
||||
}
|
||||
|
||||
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 */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ class MessageTest extends JUnitSuite with BeforeAndAfterAll {
|
|||
|
||||
@Test def shouldConvertDoubleBodyToString = {
|
||||
assertEquals("1.4", Message(1.4, null).bodyAs[String])
|
||||
assertEquals("1.4", Message(1.4, null).bodyAs(classOf[String]))
|
||||
}
|
||||
|
||||
@Test def shouldThrowExceptionWhenConvertingDoubleBodyToInputStream {
|
||||
|
|
@ -23,6 +24,17 @@ class MessageTest extends JUnitSuite with BeforeAndAfterAll {
|
|||
}
|
||||
}
|
||||
|
||||
@Test def shouldReturnDoubleHeader = {
|
||||
val message = Message("test" , Map("test" -> 1.4))
|
||||
assertEquals(1.4, message.header("test"))
|
||||
}
|
||||
|
||||
@Test def shouldConvertDoubleHeaderToString = {
|
||||
val message = Message("test" , Map("test" -> 1.4))
|
||||
assertEquals("1.4", message.headerAs[String]("test"))
|
||||
assertEquals("1.4", message.headerAs("test", classOf[String]))
|
||||
}
|
||||
|
||||
@Test def shouldReturnSubsetOfHeaders = {
|
||||
val message = Message("test" , Map("A" -> "1", "B" -> "2"))
|
||||
assertEquals(Map("B" -> "2"), message.headers(Set("B")))
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ class ProducerFeatureTest extends FeatureSpec with BeforeAndAfterAll with Before
|
|||
mockEndpoint.reset
|
||||
}
|
||||
|
||||
feature("Produce a message to a Camel endpoint") {
|
||||
feature("Produce a message to a sync Camel route") {
|
||||
|
||||
scenario("produce message and receive normal response") {
|
||||
given("a registered two-way producer")
|
||||
|
|
@ -86,9 +86,9 @@ class ProducerFeatureTest extends FeatureSpec with BeforeAndAfterAll with Before
|
|||
}
|
||||
}
|
||||
|
||||
feature("Produce a message to an async Camel endpoint") {
|
||||
feature("Produce a message to an async Camel route") {
|
||||
|
||||
scenario("produce message and async receive normal response") {
|
||||
scenario("produce message and receive normal response") {
|
||||
given("a registered two-way producer")
|
||||
val producer = actorOf(new TestProducer("direct:producer-test-3"))
|
||||
producer.start
|
||||
|
|
@ -102,7 +102,7 @@ class ProducerFeatureTest extends FeatureSpec with BeforeAndAfterAll with Before
|
|||
assert(result === Some(expected))
|
||||
}
|
||||
|
||||
scenario("produce message and async receive failure response") {
|
||||
scenario("produce message and receive failure response") {
|
||||
given("a registered two-way producer")
|
||||
val producer = actorOf(new TestProducer("direct:producer-test-3"))
|
||||
producer.start
|
||||
|
|
@ -119,9 +119,9 @@ class ProducerFeatureTest extends FeatureSpec with BeforeAndAfterAll with Before
|
|||
}
|
||||
}
|
||||
|
||||
feature("Produce a message to a Camel endpoint and then forward the result") {
|
||||
feature("Produce a message to a sync Camel route and then forward the response") {
|
||||
|
||||
scenario("produce message, forward and receive normal response") {
|
||||
scenario("produce message, forward normal response to a replying target actor and receive 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
|
||||
|
|
@ -135,7 +135,7 @@ class ProducerFeatureTest extends FeatureSpec with BeforeAndAfterAll with Before
|
|||
assert(result === Some(expected))
|
||||
}
|
||||
|
||||
scenario("produce message, forward and receive failure response") {
|
||||
scenario("produce message, forward failure response to a replying target actor and receive 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
|
||||
|
|
@ -151,7 +151,7 @@ class ProducerFeatureTest extends FeatureSpec with BeforeAndAfterAll with Before
|
|||
assert(expectedHeaders === Map(Message.MessageExchangeId -> "123", "test" -> "failure"))
|
||||
}
|
||||
|
||||
scenario("produce message, forward and produce normal response") {
|
||||
scenario("produce message, forward normal response to a producing target actor and produce response to direct:forward-test-1") {
|
||||
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
|
||||
|
|
@ -164,7 +164,7 @@ class ProducerFeatureTest extends FeatureSpec with BeforeAndAfterAll with Before
|
|||
mockEndpoint.assertIsSatisfied
|
||||
}
|
||||
|
||||
scenario("produce message, forward and produce failure response") {
|
||||
scenario("produce message, forward failure response to a producing target actor and produce response to direct:forward-test-1") {
|
||||
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
|
||||
|
|
@ -179,9 +179,9 @@ class ProducerFeatureTest extends FeatureSpec with BeforeAndAfterAll with Before
|
|||
}
|
||||
}
|
||||
|
||||
feature("Produce a message to an async Camel endpoint and then forward the result") {
|
||||
feature("Produce a message to an async Camel route and then forward the response") {
|
||||
|
||||
scenario("produce message, forward and async receive normal response") {
|
||||
scenario("produce message, forward normal response to a replying target actor and receive 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
|
||||
|
|
@ -195,7 +195,7 @@ class ProducerFeatureTest extends FeatureSpec with BeforeAndAfterAll with Before
|
|||
assert(result === Some(expected))
|
||||
}
|
||||
|
||||
scenario("produce message, forward and async receive failure response") {
|
||||
scenario("produce message, forward failure response to a replying target actor and receive 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
|
||||
|
|
@ -211,7 +211,7 @@ class ProducerFeatureTest extends FeatureSpec with BeforeAndAfterAll with Before
|
|||
assert(expectedHeaders === Map(Message.MessageExchangeId -> "123", "test" -> "failure"))
|
||||
}
|
||||
|
||||
scenario("produce message, forward and async produce normal response") {
|
||||
scenario("produce message, forward normal response to a producing target actor and produce response to direct:forward-test-1") {
|
||||
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
|
||||
|
|
@ -224,7 +224,7 @@ class ProducerFeatureTest extends FeatureSpec with BeforeAndAfterAll with Before
|
|||
mockEndpoint.assertIsSatisfied
|
||||
}
|
||||
|
||||
scenario("produce message, forward and async produce failure response") {
|
||||
scenario("produce message, forward failure response to a producing target actor and produce response to direct:forward-test-1") {
|
||||
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
|
||||
|
|
@ -298,4 +298,4 @@ object ProducerFeatureTest {
|
|||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,10 @@ class PublishRequestorTest extends JUnitSuite {
|
|||
var requestor: ActorRef = _
|
||||
var consumer: ActorRef = _
|
||||
|
||||
@Before def setUp = {
|
||||
val ascendingMethodName = (r1: ConsumerMethodRegistered, r2: ConsumerMethodRegistered) =>
|
||||
r1.method.getName < r2.method.getName
|
||||
|
||||
@Before def setUp: Unit = {
|
||||
publisher = actorOf[PublisherMock].start
|
||||
requestor = actorOf[PublishRequestor].start
|
||||
requestor ! PublishRequestorInit(publisher)
|
||||
|
|
@ -24,40 +27,58 @@ class PublishRequestorTest extends JUnitSuite {
|
|||
def endpointUri = "mock:test"
|
||||
protected def receive = null
|
||||
}).start
|
||||
|
||||
}
|
||||
|
||||
@After def tearDown = {
|
||||
AspectInitRegistry.removeListener(requestor);
|
||||
ActorRegistry.shutdownAll
|
||||
}
|
||||
|
||||
@Test def shouldReceiveConsumerMethodRegisteredEvent = {
|
||||
val obj = ActiveObject.newInstance(classOf[PojoSingle])
|
||||
val init = AspectInit(classOf[PojoSingle], null, None, 1000)
|
||||
@Test def shouldReceiveOneConsumerMethodRegisteredEvent = {
|
||||
AspectInitRegistry.addListener(requestor)
|
||||
val latch = (publisher !! SetExpectedTestMessageCount(1)).as[CountDownLatch].get
|
||||
requestor ! AspectInitRegistered(obj, init)
|
||||
val obj = TypedActor.newInstance(classOf[SampleTypedSingleConsumer], classOf[SampleTypedSingleConsumerImpl])
|
||||
assert(latch.await(5000, TimeUnit.MILLISECONDS))
|
||||
val event = (publisher !! GetRetainedMessage).get.asInstanceOf[ConsumerMethodRegistered]
|
||||
assert(event.init === init)
|
||||
val event = (publisher !! GetRetainedMessage).as[ConsumerMethodRegistered].get
|
||||
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)
|
||||
@Test def shouldReceiveOneConsumerMethodUnregisteredEvent = {
|
||||
val obj = TypedActor.newInstance(classOf[SampleTypedSingleConsumer], classOf[SampleTypedSingleConsumerImpl])
|
||||
val latch = (publisher !! SetExpectedTestMessageCount(1)).as[CountDownLatch].get
|
||||
requestor ! AspectInitUnregistered(obj, init)
|
||||
AspectInitRegistry.addListener(requestor)
|
||||
TypedActor.stop(obj)
|
||||
assert(latch.await(5000, TimeUnit.MILLISECONDS))
|
||||
val event = (publisher !! GetRetainedMessage).get.asInstanceOf[ConsumerMethodUnregistered]
|
||||
assert(event.init === init)
|
||||
val event = (publisher !! GetRetainedMessage).as[ConsumerMethodUnregistered].get
|
||||
assert(event.uri === "direct:foo")
|
||||
assert(event.activeObject === obj)
|
||||
assert(event.typedActor === obj)
|
||||
assert(event.method.getName === "foo")
|
||||
}
|
||||
|
||||
@Test def shouldReceiveConsumerRegisteredEvent = {
|
||||
@Test def shouldReceiveThreeConsumerMethodRegisteredEvents = {
|
||||
AspectInitRegistry.addListener(requestor)
|
||||
val latch = (publisher !! SetExpectedTestMessageCount(3)).as[CountDownLatch].get
|
||||
val obj = TypedActor.newInstance(classOf[SampleTypedConsumer], classOf[SampleTypedConsumerImpl])
|
||||
assert(latch.await(5000, TimeUnit.MILLISECONDS))
|
||||
val request = GetRetainedMessages(_.isInstanceOf[ConsumerMethodRegistered])
|
||||
val events = (publisher !! request).as[List[ConsumerMethodRegistered]].get
|
||||
assert(events.map(_.method.getName).sortWith(_ < _) === List("m2", "m3", "m4"))
|
||||
}
|
||||
|
||||
@Test def shouldReceiveThreeConsumerMethodUnregisteredEvents = {
|
||||
val obj = TypedActor.newInstance(classOf[SampleTypedConsumer], classOf[SampleTypedConsumerImpl])
|
||||
val latch = (publisher !! SetExpectedTestMessageCount(3)).as[CountDownLatch].get
|
||||
AspectInitRegistry.addListener(requestor)
|
||||
TypedActor.stop(obj)
|
||||
assert(latch.await(5000, TimeUnit.MILLISECONDS))
|
||||
val request = GetRetainedMessages(_.isInstanceOf[ConsumerMethodUnregistered])
|
||||
val events = (publisher !! request).as[List[ConsumerMethodUnregistered]].get
|
||||
assert(events.map(_.method.getName).sortWith(_ < _) === List("m2", "m3", "m4"))
|
||||
}
|
||||
|
||||
@Test def shouldReceiveOneConsumerRegisteredEvent = {
|
||||
val latch = (publisher !! SetExpectedTestMessageCount(1)).as[CountDownLatch].get
|
||||
requestor ! ActorRegistered(consumer)
|
||||
assert(latch.await(5000, TimeUnit.MILLISECONDS))
|
||||
|
|
@ -65,7 +86,7 @@ class PublishRequestorTest extends JUnitSuite {
|
|||
Some(ConsumerRegistered(consumer, "mock:test", consumer.uuid, false)))
|
||||
}
|
||||
|
||||
@Test def shouldReceiveConsumerUnregisteredEvent = {
|
||||
@Test def shouldReceiveOneConsumerUnregisteredEvent = {
|
||||
val latch = (publisher !! SetExpectedTestMessageCount(1)).as[CountDownLatch].get
|
||||
requestor ! ActorUnregistered(consumer)
|
||||
assert(latch.await(5000, TimeUnit.MILLISECONDS))
|
||||
|
|
|
|||
|
|
@ -4,24 +4,23 @@ import java.util.concurrent.{CountDownLatch, TimeUnit}
|
|||
|
||||
import org.scalatest.{GivenWhenThen, BeforeAndAfterAll, FeatureSpec}
|
||||
|
||||
import se.scalablesolutions.akka.actor._
|
||||
import se.scalablesolutions.akka.actor.Actor._
|
||||
import se.scalablesolutions.akka.actor.{ActiveObject, ActorRegistry, RemoteActor}
|
||||
import se.scalablesolutions.akka.remote.{RemoteClient, RemoteServer}
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
class RemoteConsumerTest extends FeatureSpec with BeforeAndAfterAll with GivenWhenThen {
|
||||
import CamelServiceManager._
|
||||
import RemoteConsumerTest._
|
||||
|
||||
var service: CamelService = _
|
||||
var server: RemoteServer = _
|
||||
|
||||
override protected def beforeAll = {
|
||||
ActorRegistry.shutdownAll
|
||||
|
||||
service = CamelService.newInstance
|
||||
service.load
|
||||
startCamelService
|
||||
|
||||
server = new RemoteServer()
|
||||
server.start(host, port)
|
||||
|
|
@ -31,7 +30,8 @@ class RemoteConsumerTest extends FeatureSpec with BeforeAndAfterAll with GivenWh
|
|||
|
||||
override protected def afterAll = {
|
||||
server.shutdown
|
||||
service.unload
|
||||
|
||||
stopCamelService
|
||||
|
||||
RemoteClient.shutdownAll
|
||||
ActorRegistry.shutdownAll
|
||||
|
|
@ -39,35 +39,51 @@ class RemoteConsumerTest extends FeatureSpec with BeforeAndAfterAll with GivenWh
|
|||
Thread.sleep(1000)
|
||||
}
|
||||
|
||||
feature("Client-initiated remote consumer actor") {
|
||||
scenario("access published remote consumer actor") {
|
||||
given("a client-initiated remote consumer actor")
|
||||
feature("Publish consumer on remote node") {
|
||||
scenario("access published remote consumer") {
|
||||
given("a client-initiated remote consumer")
|
||||
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))
|
||||
|
||||
then("the published actor is accessible via its endpoint URI")
|
||||
val response = CamelContextManager.template.requestBody("direct:remote-actor", "test")
|
||||
then("the published consumer is accessible via its endpoint URI")
|
||||
val response = CamelContextManager.template.requestBody("direct:remote-consumer", "test")
|
||||
assert(response === "remote actor: test")
|
||||
}
|
||||
}
|
||||
|
||||
feature("Client-initiated remote consumer active object") {
|
||||
feature("Publish typed consumer on remote node") {
|
||||
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 typed consumer")
|
||||
val consumer = TypedActor.newRemoteInstance(classOf[SampleRemoteTypedConsumer], classOf[SampleRemoteTypedConsumerImpl], host, port)
|
||||
|
||||
when("remote consumer publication is triggered")
|
||||
val latch = (service.consumerPublisher !! SetExpectedMessageCount(1)).as[CountDownLatch].get
|
||||
when("remote typed consumer publication is triggered")
|
||||
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-consumer", "test")
|
||||
assert(response === "remote typed actor: test")
|
||||
}
|
||||
}
|
||||
|
||||
feature("Publish untyped consumer on remote node") {
|
||||
scenario("access published remote untyped consumer") {
|
||||
given("a client-initiated remote untyped consumer")
|
||||
val consumer = UntypedActor.actorOf(classOf[SampleRemoteUntypedConsumer]).start
|
||||
|
||||
when("remote untyped consumer publication is triggered")
|
||||
var latch = service.expectEndpointActivationCount(1)
|
||||
consumer.sendRequestReply(Message("init", Map("test" -> "init")))
|
||||
assert(latch.await(5000, TimeUnit.MILLISECONDS))
|
||||
|
||||
then("the published untyped consumer is accessible via its endpoint URI")
|
||||
val response = CamelContextManager.template.requestBodyAndHeader("direct:remote-untyped-consumer", "a", "test", "b")
|
||||
assert(response === "a b")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -77,7 +93,7 @@ object RemoteConsumerTest {
|
|||
val port = 7774
|
||||
|
||||
class RemoteConsumer extends RemoteActor(host, port) with Consumer {
|
||||
def endpointUri = "direct:remote-actor"
|
||||
def endpointUri = "direct:remote-consumer"
|
||||
|
||||
protected def receive = {
|
||||
case "init" => self.reply("done")
|
||||
|
|
|
|||
98
akka-camel/src/test/scala/UntypedProducerFeatureTest.scala
Normal file
98
akka-camel/src/test/scala/UntypedProducerFeatureTest.scala
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
package se.scalablesolutions.akka.camel
|
||||
|
||||
import org.apache.camel.{Exchange, Processor}
|
||||
import org.apache.camel.builder.RouteBuilder
|
||||
import org.apache.camel.component.mock.MockEndpoint
|
||||
import org.scalatest.{GivenWhenThen, BeforeAndAfterEach, BeforeAndAfterAll, FeatureSpec}
|
||||
|
||||
import se.scalablesolutions.akka.actor.UntypedActor._
|
||||
import se.scalablesolutions.akka.actor.ActorRegistry
|
||||
|
||||
class UntypedProducerFeatureTest extends FeatureSpec with BeforeAndAfterAll with BeforeAndAfterEach with GivenWhenThen {
|
||||
import UntypedProducerFeatureTest._
|
||||
|
||||
override protected def beforeAll = {
|
||||
ActorRegistry.shutdownAll
|
||||
CamelContextManager.init
|
||||
CamelContextManager.context.addRoutes(new TestRoute)
|
||||
CamelContextManager.start
|
||||
}
|
||||
|
||||
override protected def afterAll = {
|
||||
CamelContextManager.stop
|
||||
ActorRegistry.shutdownAll
|
||||
}
|
||||
|
||||
override protected def afterEach = {
|
||||
mockEndpoint.reset
|
||||
}
|
||||
|
||||
feature("Produce a message to a sync Camel route") {
|
||||
|
||||
scenario("produce message and receive normal response") {
|
||||
given("a registered two-way producer")
|
||||
val producer = actorOf(classOf[SampleUntypedReplyingProducer])
|
||||
producer.start
|
||||
|
||||
when("a test message is sent to the producer with !!")
|
||||
val message = Message("test", Map(Message.MessageExchangeId -> "123"))
|
||||
val result = producer.sendRequestReply(message)
|
||||
|
||||
then("a normal response should have been returned by the producer")
|
||||
val expected = Message("received test", Map(Message.MessageExchangeId -> "123"))
|
||||
assert(result === expected)
|
||||
}
|
||||
|
||||
scenario("produce message and receive failure response") {
|
||||
given("a registered two-way producer")
|
||||
val producer = actorOf(classOf[SampleUntypedReplyingProducer])
|
||||
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.sendRequestReply(message).asInstanceOf[Failure]
|
||||
|
||||
then("a failure response should have been returned by the producer")
|
||||
val expectedFailureText = result.cause.getMessage
|
||||
val expectedHeaders = result.headers
|
||||
assert(expectedFailureText === "failure")
|
||||
assert(expectedHeaders === Map(Message.MessageExchangeId -> "123"))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
feature("Produce a message to a sync Camel route and then forward the response") {
|
||||
|
||||
scenario("produce message and send normal response to direct:forward-test-1") {
|
||||
given("a registered one-way producer configured with a forward target")
|
||||
val producer = actorOf(classOf[SampleUntypedForwardingProducer])
|
||||
producer.start
|
||||
|
||||
when("a test message is sent to the producer with !")
|
||||
mockEndpoint.expectedBodiesReceived("received test")
|
||||
val result = producer.sendOneWay(Message("test"), producer)
|
||||
|
||||
then("a normal response should have been sent")
|
||||
mockEndpoint.assertIsSatisfied
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private def mockEndpoint = CamelContextManager.context.getEndpoint("mock:mock", classOf[MockEndpoint])
|
||||
}
|
||||
|
||||
object UntypedProducerFeatureTest {
|
||||
class TestRoute extends RouteBuilder {
|
||||
def configure {
|
||||
from("direct:forward-test-1").to("mock:mock")
|
||||
from("direct:producer-test-1").process(new Processor() {
|
||||
def process(exchange: Exchange) = {
|
||||
exchange.getIn.getBody match {
|
||||
case "fail" => throw new Exception("failure")
|
||||
case body => exchange.getOut.setBody("received %s" format body)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,105 +0,0 @@
|
|||
package se.scalablesolutions.akka.camel.component
|
||||
|
||||
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.camel._
|
||||
import org.apache.camel.impl.{DefaultCamelContext, SimpleRegistry}
|
||||
import org.apache.camel.{ResolveEndpointFailedException, ExchangePattern, Exchange, Processor}
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
class ActiveObjectComponentFeatureTest extends FeatureSpec with BeforeAndAfterAll with BeforeAndAfterEach {
|
||||
import ActiveObjectComponentFeatureTest._
|
||||
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 registry = new SimpleRegistry
|
||||
registry.put("pojo", activePojo)
|
||||
|
||||
CamelContextManager.init(new DefaultCamelContext(registry))
|
||||
CamelContextManager.context.addRoutes(new CustomRouteBuilder)
|
||||
CamelContextManager.start
|
||||
|
||||
CamelContextManager.activeObjectRegistry.put("base", activePojoBase)
|
||||
CamelContextManager.activeObjectRegistry.put("intf", activePojoIntf)
|
||||
}
|
||||
|
||||
override protected def afterAll = {
|
||||
CamelContextManager.stop
|
||||
ActorRegistry.shutdownAll
|
||||
}
|
||||
|
||||
feature("Communicate with an active object from a Camel application using active object endpoint URIs") {
|
||||
import ActiveObjectComponent.InternalSchema
|
||||
import ExchangePattern._
|
||||
|
||||
scenario("in-out exchange with proxy created from interface and method returning String") {
|
||||
val result = template.requestBodyAndHeader("%s:intf?method=m2" format InternalSchema, "x", "test", "y")
|
||||
assert(result === "m2impl: x y")
|
||||
}
|
||||
|
||||
scenario("in-out exchange with proxy created from class and method returning String") {
|
||||
val result = template.requestBodyAndHeader("%s:base?method=m2" format InternalSchema, "x", "test", "y")
|
||||
assert(result === "m2base: x y")
|
||||
}
|
||||
|
||||
scenario("in-out exchange with proxy created from class and method returning void") {
|
||||
val result = template.requestBodyAndHeader("%s:base?method=m5" format InternalSchema, "x", "test", "y")
|
||||
assert(result === "x") // returns initial body
|
||||
}
|
||||
|
||||
scenario("in-only exchange with proxy created from class and method returning String") {
|
||||
val result = template.send("%s:base?method=m2" format InternalSchema, InOnly, new Processor {
|
||||
def process(exchange: Exchange) = {
|
||||
exchange.getIn.setBody("x")
|
||||
exchange.getIn.setHeader("test", "y")
|
||||
}
|
||||
});
|
||||
assert(result.getPattern === InOnly)
|
||||
assert(result.getIn.getBody === "m2base: x y")
|
||||
assert(result.getOut.getBody === null)
|
||||
}
|
||||
|
||||
scenario("in-only exchange with proxy created from class and method returning void") {
|
||||
val result = template.send("%s:base?method=m5" format InternalSchema, InOnly, new Processor {
|
||||
def process(exchange: Exchange) = {
|
||||
exchange.getIn.setBody("x")
|
||||
exchange.getIn.setHeader("test", "y")
|
||||
}
|
||||
});
|
||||
assert(result.getPattern === InOnly)
|
||||
assert(result.getIn.getBody === "x")
|
||||
assert(result.getOut.getBody === null)
|
||||
}
|
||||
}
|
||||
|
||||
feature("Communicate with an active object from a Camel application from a custom Camel route") {
|
||||
|
||||
scenario("in-out exchange with externally registered active object") {
|
||||
val result = template.requestBody("direct:test", "test")
|
||||
assert(result === "foo: test")
|
||||
}
|
||||
|
||||
scenario("in-out exchange with internally registered active object not possible") {
|
||||
intercept[ResolveEndpointFailedException] {
|
||||
template.requestBodyAndHeader("active-object:intf?method=m2", "x", "test", "y")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object ActiveObjectComponentFeatureTest {
|
||||
class CustomRouteBuilder extends RouteBuilder {
|
||||
def configure = {
|
||||
from("direct:test").to("active-object:pojo?method=foo")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,37 +3,36 @@ 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") {
|
||||
feature("Communicate with an actor via an actor:uuid endpoint") {
|
||||
import CamelContextManager.template
|
||||
|
||||
scenario("one-way communication using actor id") {
|
||||
val actor = actorOf[Tester1].start
|
||||
val latch = (actor !! SetExpectedMessageCount(1)).as[CountDownLatch].get
|
||||
template.sendBody("actor:%s" format actor.id, "Martin")
|
||||
assert(latch.await(5000, TimeUnit.MILLISECONDS))
|
||||
val reply = (actor !! GetRetainedMessage).get.asInstanceOf[Message]
|
||||
assert(reply.body === "Martin")
|
||||
}
|
||||
|
||||
scenario("one-way communication using actor uuid") {
|
||||
scenario("one-way communication") {
|
||||
val actor = actorOf[Tester1].start
|
||||
val latch = (actor !! SetExpectedMessageCount(1)).as[CountDownLatch].get
|
||||
template.sendBody("actor:uuid:%s" format actor.uuid, "Martin")
|
||||
|
|
@ -42,12 +41,7 @@ class ActorComponentFeatureTest extends FeatureSpec with BeforeAndAfterAll with
|
|||
assert(reply.body === "Martin")
|
||||
}
|
||||
|
||||
scenario("two-way communication using actor id") {
|
||||
val actor = actorOf[Tester2].start
|
||||
assert(template.requestBody("actor:%s" format actor.id, "Martin") === "Hello Martin")
|
||||
}
|
||||
|
||||
scenario("two-way communication using actor uuid") {
|
||||
scenario("two-way communication") {
|
||||
val actor = actorOf[Tester2].start
|
||||
assert(template.requestBody("actor:uuid:%s" format actor.uuid, "Martin") === "Hello Martin")
|
||||
}
|
||||
|
|
@ -58,5 +52,79 @@ class ActorComponentFeatureTest extends FeatureSpec with BeforeAndAfterAll with
|
|||
template.requestBody("actor:uuid:%s?blocking=true" format actor.uuid, "Martin")
|
||||
}
|
||||
}
|
||||
|
||||
scenario("two-way communication via a custom route with failure response") {
|
||||
mockEndpoint.expectedBodiesReceived("whatever")
|
||||
template.requestBody("direct:failure-test-1", "whatever")
|
||||
mockEndpoint.assertIsSatisfied
|
||||
}
|
||||
|
||||
scenario("two-way communication via a custom route with exception") {
|
||||
mockEndpoint.expectedBodiesReceived("whatever")
|
||||
template.requestBody("direct:failure-test-2", "whatever")
|
||||
mockEndpoint.assertIsSatisfied
|
||||
}
|
||||
}
|
||||
|
||||
feature("Communicate with an actor via an actor:id endpoint") {
|
||||
import CamelContextManager.template
|
||||
|
||||
scenario("one-way communication") {
|
||||
val actor = actorOf[Tester1].start
|
||||
val latch = (actor !! SetExpectedMessageCount(1)).as[CountDownLatch].get
|
||||
template.sendBody("actor:%s" format actor.id, "Martin")
|
||||
assert(latch.await(5000, TimeUnit.MILLISECONDS))
|
||||
val reply = (actor !! GetRetainedMessage).get.asInstanceOf[Message]
|
||||
assert(reply.body === "Martin")
|
||||
}
|
||||
|
||||
scenario("two-way communication") {
|
||||
val actor = actorOf[Tester2].start
|
||||
assert(template.requestBody("actor:%s" format actor.id, "Martin") === "Hello Martin")
|
||||
}
|
||||
|
||||
scenario("two-way communication via a custom route") {
|
||||
val actor = actorOf[CustomIdActor].start
|
||||
assert(template.requestBody("direct:custom-id-test-1", "Martin") === "Received Martin")
|
||||
assert(template.requestBody("direct:custom-id-test-2", "Martin") === "Received Martin")
|
||||
}
|
||||
}
|
||||
|
||||
private def mockEndpoint = CamelContextManager.context.getEndpoint("mock:mock", classOf[MockEndpoint])
|
||||
}
|
||||
|
||||
object ActorComponentFeatureTest {
|
||||
class CustomIdActor extends Actor {
|
||||
self.id = "custom-id"
|
||||
protected def receive = {
|
||||
case msg: Message => self.reply("Received %s" format msg.body)
|
||||
}
|
||||
}
|
||||
|
||||
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:custom-id-test-1").to("actor:custom-id")
|
||||
from("direct:custom-id-test-2").to("actor:id:custom-id")
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -116,4 +116,4 @@ object ActorProducerTest {
|
|||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,109 @@
|
|||
package se.scalablesolutions.akka.camel.component
|
||||
|
||||
import org.scalatest.{BeforeAndAfterEach, BeforeAndAfterAll, FeatureSpec}
|
||||
|
||||
import org.apache.camel.builder.RouteBuilder
|
||||
import se.scalablesolutions.akka.actor.Actor._
|
||||
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}
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
class TypedActorComponentFeatureTest extends FeatureSpec with BeforeAndAfterAll with BeforeAndAfterEach {
|
||||
import TypedActorComponentFeatureTest._
|
||||
import CamelContextManager.template
|
||||
|
||||
override protected def beforeAll = {
|
||||
val typedActor = TypedActor.newInstance(classOf[SampleTypedActor], classOf[SampleTypedActorImpl]) // not a consumer
|
||||
val typedConsumer = TypedActor.newInstance(classOf[SampleTypedConsumer], classOf[SampleTypedConsumerImpl])
|
||||
|
||||
val registry = new SimpleRegistry
|
||||
// external registration
|
||||
registry.put("ta", typedActor)
|
||||
|
||||
CamelContextManager.init(new DefaultCamelContext(registry))
|
||||
CamelContextManager.context.addRoutes(new CustomRouteBuilder)
|
||||
CamelContextManager.start
|
||||
|
||||
// Internal registration
|
||||
CamelContextManager.typedActorRegistry.put("tc", typedConsumer)
|
||||
}
|
||||
|
||||
override protected def afterAll = {
|
||||
CamelContextManager.stop
|
||||
ActorRegistry.shutdownAll
|
||||
}
|
||||
|
||||
feature("Communicate with an internally-registered typed actor using typed-actor-internal endpoint URIs") {
|
||||
import TypedActorComponent.InternalSchema
|
||||
import ExchangePattern._
|
||||
|
||||
scenario("two-way communication with method returning String") {
|
||||
val result1 = template.requestBodyAndHeader("%s:tc?method=m2" format InternalSchema, "x", "test", "y")
|
||||
val result2 = template.requestBodyAndHeader("%s:tc?method=m4" format InternalSchema, "x", "test", "y")
|
||||
assert(result1 === "m2: x y")
|
||||
assert(result2 === "m4: x y")
|
||||
}
|
||||
|
||||
scenario("two-way communication with method returning void") {
|
||||
val result = template.requestBodyAndHeader("%s:tc?method=m5" format InternalSchema, "x", "test", "y")
|
||||
assert(result === "x") // returns initial body
|
||||
}
|
||||
|
||||
scenario("one-way communication with method returning String") {
|
||||
val result = template.send("%s:tc?method=m2" format InternalSchema, InOnly, new Processor {
|
||||
def process(exchange: Exchange) = {
|
||||
exchange.getIn.setBody("x")
|
||||
exchange.getIn.setHeader("test", "y")
|
||||
}
|
||||
});
|
||||
assert(result.getPattern === InOnly)
|
||||
assert(result.getIn.getBody === "m2: x y")
|
||||
assert(result.getOut.getBody === null)
|
||||
}
|
||||
|
||||
scenario("one-way communication with method returning void") {
|
||||
val result = template.send("%s:tc?method=m5" format InternalSchema, InOnly, new Processor {
|
||||
def process(exchange: Exchange) = {
|
||||
exchange.getIn.setBody("x")
|
||||
exchange.getIn.setHeader("test", "y")
|
||||
}
|
||||
});
|
||||
assert(result.getPattern === InOnly)
|
||||
assert(result.getIn.getBody === "x")
|
||||
assert(result.getOut.getBody === null)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
feature("Communicate with an internally-registered typed actor using typed-actor endpoint URIs") {
|
||||
scenario("communication not possible") {
|
||||
intercept[ResolveEndpointFailedException] {
|
||||
template.requestBodyAndHeader("typed-actor:tc?method=m2", "x", "test", "y")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
feature("Communicate with an externally-registered typed actor using typed-actor endpoint URIs") {
|
||||
scenario("two-way communication with method returning String") {
|
||||
val result = template.requestBody("typed-actor:ta?method=foo", "test")
|
||||
assert(result === "foo: test")
|
||||
}
|
||||
|
||||
scenario("two-way communication with method returning String via custom route") {
|
||||
val result = template.requestBody("direct:test", "test")
|
||||
assert(result === "foo: test")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object TypedActorComponentFeatureTest {
|
||||
class CustomRouteBuilder extends RouteBuilder {
|
||||
def configure = {
|
||||
from("direct:test").to("typed-actor:ta?method=foo")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,8 @@ package se.scalablesolutions.akka.camel.support
|
|||
|
||||
import java.util.concurrent.{TimeUnit, CountDownLatch}
|
||||
|
||||
import collection.mutable.Buffer
|
||||
|
||||
import se.scalablesolutions.akka.camel.Message
|
||||
import se.scalablesolutions.akka.actor.Actor
|
||||
|
||||
|
|
@ -54,12 +56,13 @@ trait Respond { this: Actor =>
|
|||
}
|
||||
|
||||
trait Retain { this: Actor =>
|
||||
var message: Any = _
|
||||
val messages = Buffer[Any]()
|
||||
|
||||
def retain: Handler = {
|
||||
case GetRetainedMessage => self.reply(message)
|
||||
case GetRetainedMessage => self.reply(messages.last)
|
||||
case GetRetainedMessages(p) => self.reply(messages.toList.filter(p))
|
||||
case msg => {
|
||||
message = msg
|
||||
messages += msg
|
||||
msg
|
||||
}
|
||||
}
|
||||
|
|
@ -73,3 +76,6 @@ trait Noop { this: Actor =>
|
|||
|
||||
case class SetExpectedMessageCount(num: Int)
|
||||
case class GetRetainedMessage()
|
||||
case class GetRetainedMessages(p: Any => Boolean) {
|
||||
def this() = this(_ => true)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
(
|
||||
|
||||
;; Where you unpacked the ENSIME distribution.
|
||||
:server-root "/home/jboner/emacs-config/lib/ensime"
|
||||
:server-root "/Users/jboner/config/emacs-config/lib/ensime"
|
||||
|
||||
;; The command with which to invoke the ENSIME server. Change this to
|
||||
;; "bin/server.bat" if your're on Windows.
|
||||
:server-cmd "bin/server.bat"
|
||||
:server-cmd "bin/server.sh"
|
||||
|
||||
|
||||
;; The host to connect to. Connecting to remote ENSIME servers is not
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -52,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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -81,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.
|
||||
*/
|
||||
|
|
@ -115,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>
|
||||
|
|
|
|||
31
akka-core/src/main/resources/logback.xml
Executable file
31
akka-core/src/main/resources/logback.xml
Executable file
|
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!-- For assistance related to logback-translator or configuration -->
|
||||
<!-- files in general, please contact the logback user mailing list -->
|
||||
<!-- at http://www.qos.ch/mailman/listinfo/logback-user -->
|
||||
<!-- -->
|
||||
<!-- For professional support please see -->
|
||||
<!-- http://www.qos.ch/shop/products/professionalSupport -->
|
||||
<!-- -->
|
||||
<!-- FOR AKKA INTERNAL USE ONLY -->
|
||||
<configuration scan="false" debug="false">
|
||||
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>[%4p] [%d{ISO8601}] [%t] %c{1}: %m%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
<appender name="R" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<File>./logs/akka.log</File>
|
||||
<encoder>
|
||||
<pattern>[%4p] [%d{ISO8601}] [%t] %c{1}: %m%n</pattern>
|
||||
</encoder>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>./logs/akka.log.%d{yyyy-MM-dd-HH}</fileNamePattern>
|
||||
</rollingPolicy>
|
||||
</appender>
|
||||
<logger name="se.scalablesolutions" level="DEBUG"/>
|
||||
<root level="INFO">
|
||||
<appender-ref ref="stdout"/>
|
||||
<appender-ref ref="R"/>
|
||||
</root>
|
||||
</configuration>
|
||||
|
|
@ -1,852 +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.ifTrace("Can't set 'ActiveObjectContext' for ActiveObject [" +
|
||||
activeObject.getClass.getName +
|
||||
"] since no field of this type could be found.")
|
||||
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 [" +
|
||||
"\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]()
|
||||
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
|
||||
|
||||
if (self.lifeCycle.isEmpty) self.lifeCycle = Some(LifeCycle(Permanent))
|
||||
|
||||
// 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 @ Invocation(joinPoint, isOneWay, _, sender, senderFuture) =>
|
||||
ActiveObject.log.ifTrace("Invoking active object with message:\n" + invocation)
|
||||
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)
|
||||
preRestart.foreach(_.invoke(target.get, ZERO_ITEM_OBJECT_ARRAY: _*))
|
||||
} catch { case e: InvocationTargetException => throw e.getCause }
|
||||
}
|
||||
|
||||
override def postRestart(reason: Throwable) {
|
||||
try {
|
||||
postRestart.foreach(_.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 {
|
||||
zhutdown.foreach(_.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,15 @@ 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 se.scalablesolutions.akka.AkkaException
|
||||
|
||||
import com.google.protobuf.Message
|
||||
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.net.InetSocketAddress
|
||||
|
||||
import scala.reflect.BeanProperty
|
||||
|
||||
/**
|
||||
* Implements the Transactor abstraction. E.g. a transactional actor.
|
||||
|
|
@ -32,29 +37,42 @@ 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)
|
||||
}
|
||||
|
||||
/**
|
||||
* Life-cycle messages for the Actors
|
||||
*/
|
||||
@serializable sealed trait LifeCycleMessage
|
||||
|
||||
case class HotSwap(code: Option[Actor.Receive]) extends LifeCycleMessage
|
||||
|
||||
case class Restart(reason: Throwable) extends LifeCycleMessage
|
||||
|
||||
case class Exit(dead: ActorRef, killer: Throwable) extends LifeCycleMessage
|
||||
|
||||
case class Link(child: ActorRef) extends LifeCycleMessage
|
||||
|
||||
case class Unlink(child: ActorRef) extends LifeCycleMessage
|
||||
|
||||
case class UnlinkAndStop(child: ActorRef) extends LifeCycleMessage
|
||||
|
||||
case object ReceiveTimeout extends LifeCycleMessage
|
||||
|
||||
case class MaximumNumberOfRestartsWithinTimeRangeReached(
|
||||
victim: ActorRef, maxNrOfRetries: Int, withinTimeRange: Int, lastExceptionCausingRestart: Throwable) extends LifeCycleMessage
|
||||
@BeanProperty val victim: ActorRef,
|
||||
@BeanProperty val maxNrOfRetries: Int,
|
||||
@BeanProperty val withinTimeRange: Int,
|
||||
@BeanProperty val 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 ActorStartException private[akka](message: String) extends AkkaException(message)
|
||||
class IllegalActorStateException private[akka](message: String) extends AkkaException(message)
|
||||
class ActorKilledException private[akka](message: String) extends AkkaException(message)
|
||||
class ActorInitializationException private[akka](message: String) extends AkkaException(message)
|
||||
class ActorTimeoutException private[akka](message: String) extends AkkaException(message)
|
||||
|
||||
/**
|
||||
* Actor factory module with factory methods for creating various kinds of Actors.
|
||||
|
|
@ -62,7 +80,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)
|
||||
|
||||
/**
|
||||
|
|
@ -74,7 +92,7 @@ object Actor extends Logging {
|
|||
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]
|
||||
|
|
@ -87,10 +105,27 @@ object Actor extends Logging {
|
|||
* val actor = actorOf[MyActor].start
|
||||
* </pre>
|
||||
*/
|
||||
def actorOf[T <: Actor : Manifest]: ActorRef = new LocalActorRef(manifest[T].erasure.asInstanceOf[Class[_ <: Actor]])
|
||||
def actorOf[T <: Actor : Manifest]: ActorRef = actorOf(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 with type T.
|
||||
* <pre>
|
||||
* import Actor._
|
||||
* val actor = actorOf[MyActor]
|
||||
* actor.start
|
||||
* actor ! message
|
||||
* actor.stop
|
||||
* </pre>
|
||||
* You can create and start the actor in one statement like this:
|
||||
* <pre>
|
||||
* val actor = actorOf[MyActor].start
|
||||
* </pre>
|
||||
*/
|
||||
def actorOf(clazz: Class[_ <: Actor]): ActorRef = new LocalActorRef(clazz)
|
||||
|
||||
|
||||
/**
|
||||
* 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/>
|
||||
|
|
@ -221,7 +256,7 @@ object Actor extends Logging {
|
|||
case object Spawn
|
||||
actorOf(new Actor() {
|
||||
def receive = {
|
||||
case Spawn => body; self.stop
|
||||
case Spawn => try { body } finally { self.stop }
|
||||
}
|
||||
}).start ! Spawn
|
||||
}
|
||||
|
|
@ -292,15 +327,14 @@ trait Actor extends Logging {
|
|||
type Receive = Actor.Receive
|
||||
|
||||
/*
|
||||
* Option[ActorRef] representation of the 'self' ActorRef reference.
|
||||
* <p/>
|
||||
* Mainly for internal use, functions as the implicit sender references when invoking
|
||||
* one of the message send functions ('!', '!!' and '!!!').
|
||||
*/
|
||||
@transient implicit val optionSelf: Option[ActorRef] = {
|
||||
val ref = Actor.actorRefInCreation.value
|
||||
Actor.actorRefInCreation.value = None
|
||||
if (ref.isEmpty) throw new ActorInitializationException(
|
||||
* Some[ActorRef] representation of the 'self' ActorRef reference.
|
||||
* <p/>
|
||||
* Mainly for internal use, functions as the implicit sender references when invoking
|
||||
* the 'forward' function.
|
||||
*/
|
||||
@transient implicit val someSelf: Some[ActorRef] = {
|
||||
val optRef = Actor.actorRefInCreation.value
|
||||
if (optRef.isEmpty) throw new ActorInitializationException(
|
||||
"ActorRef for instance of actor [" + getClass.getName + "] is not in scope." +
|
||||
"\n\tYou can not create an instance of an actor explicitly using 'new MyActor'." +
|
||||
"\n\tYou have to use one of the factory methods in the 'Actor' object to create a new actor." +
|
||||
|
|
@ -308,16 +342,19 @@ trait Actor extends Logging {
|
|||
"\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
|
||||
|
||||
val ref = optRef.asInstanceOf[Some[ActorRef]].get
|
||||
ref.id = getClass.getName //FIXME: Is this needed?
|
||||
optRef.asInstanceOf[Some[ActorRef]]
|
||||
}
|
||||
|
||||
/*
|
||||
* Some[ActorRef] representation of the 'self' ActorRef reference.
|
||||
/*
|
||||
* Option[ActorRef] representation of the 'self' ActorRef reference.
|
||||
* <p/>
|
||||
* Mainly for internal use, functions as the implicit sender references when invoking
|
||||
* the 'forward' function.
|
||||
* one of the message send functions ('!', '!!' and '!!!').
|
||||
*/
|
||||
@transient implicit val someSelf: Some[ActorRef] = optionSelf.asInstanceOf[Some[ActorRef]]
|
||||
implicit def optionSelf: Option[ActorRef] = someSelf
|
||||
|
||||
/**
|
||||
* The 'self' field holds the ActorRef for this actor.
|
||||
|
|
@ -346,11 +383,7 @@ trait Actor extends Logging {
|
|||
* self.stop(..)
|
||||
* </pre>
|
||||
*/
|
||||
@transient val self: ActorRef = {
|
||||
val zelf = optionSelf.get
|
||||
zelf.id = getClass.getName
|
||||
zelf
|
||||
}
|
||||
@transient val self: ScalaActorRef = someSelf.get
|
||||
|
||||
/**
|
||||
* User overridable callback/setting.
|
||||
|
|
@ -413,26 +446,45 @@ trait Actor extends Logging {
|
|||
/**
|
||||
* Is the actor able to handle the message passed in as arguments?
|
||||
*/
|
||||
def isDefinedAt(message: Any): Boolean = base.isDefinedAt(message)
|
||||
def isDefinedAt(message: Any): Boolean = processingBehavior.isDefinedAt(message)
|
||||
|
||||
/** One of the fundamental methods of the ActorsModel
|
||||
* Actor assumes a new behavior,
|
||||
* None reverts the current behavior to the original behavior
|
||||
*/
|
||||
def become(behavior: Option[Receive]) {
|
||||
self.hotswap = behavior
|
||||
self.checkReceiveTimeout // FIXME : how to reschedule receivetimeout on hotswap?
|
||||
}
|
||||
|
||||
/** Akka Java API
|
||||
* One of the fundamental methods of the ActorsModel
|
||||
* Actor assumes a new behavior,
|
||||
* null reverts the current behavior to the original behavior
|
||||
*/
|
||||
def become(behavior: Receive): Unit = become(Option(behavior))
|
||||
|
||||
// =========================================
|
||||
// ==== INTERNAL IMPLEMENTATION DETAILS ====
|
||||
// =========================================
|
||||
|
||||
private[akka] def apply(msg: Any) = processingBehavior(msg)
|
||||
|
||||
private[akka] def base: Receive = try {
|
||||
lifeCycles orElse (self.hotswap getOrElse receive)
|
||||
} catch {
|
||||
case e: NullPointerException => throw new IllegalActorStateException(
|
||||
"The 'self' ActorRef reference for [" + getClass.getName + "] is NULL, error in the ActorRef initialization process.")
|
||||
}
|
||||
|
||||
private val lifeCycles: Receive = {
|
||||
case HotSwap(code) => self.hotswap = code; self.checkReceiveTimeout // FIXME : how to reschedule receivetimeout on hotswap?
|
||||
case Exit(dead, reason) => self.handleTrapExit(dead, reason)
|
||||
case Link(child) => self.link(child)
|
||||
case Unlink(child) => self.unlink(child)
|
||||
case UnlinkAndStop(child) => self.unlink(child); child.stop
|
||||
case Restart(reason) => throw reason
|
||||
private lazy val processingBehavior: Receive = {
|
||||
lazy val defaultBehavior = receive
|
||||
val actorBehavior: Receive = {
|
||||
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 Restart(reason) => throw reason
|
||||
case msg if self.hotswap.isDefined &&
|
||||
self.hotswap.get.isDefinedAt(msg) => self.hotswap.get.apply(msg)
|
||||
case msg if self.hotswap.isEmpty &&
|
||||
defaultBehavior.isDefinedAt(msg) => defaultBehavior.apply(msg)
|
||||
}
|
||||
actorBehavior
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -29,19 +29,15 @@ 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]]
|
||||
|
||||
private val Naught = Array[ActorRef]() //Nil for Arrays
|
||||
|
||||
/**
|
||||
* Returns all actors in the system.
|
||||
*/
|
||||
def actors: List[ActorRef] = filter(_ => true)
|
||||
def actors: Array[ActorRef] = filter(_ => true)
|
||||
|
||||
/**
|
||||
* Invokes a function for all actors.
|
||||
|
|
@ -51,16 +47,30 @@ object ActorRegistry extends ListenerManagement {
|
|||
while (elements.hasMoreElements) f(elements.nextElement)
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the function on all known actors until it returns Some
|
||||
* Returns None if the function never returns Some
|
||||
*/
|
||||
def find[T](f: PartialFunction[ActorRef,T]) : Option[T] = {
|
||||
val elements = actorsByUUID.elements
|
||||
while (elements.hasMoreElements) {
|
||||
val element = elements.nextElement
|
||||
if(f isDefinedAt element)
|
||||
return Some(f(element))
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all actors that are subtypes of the class passed in as the Manifest argument and supproting passed message.
|
||||
*/
|
||||
def actorsFor[T <: Actor](message: Any)(implicit manifest: Manifest[T] ): List[ActorRef] =
|
||||
def actorsFor[T <: Actor](message: Any)(implicit manifest: Manifest[T] ): Array[ActorRef] =
|
||||
filter(a => manifest.erasure.isAssignableFrom(a.actor.getClass) && a.isDefinedAt(message))
|
||||
|
||||
/**
|
||||
* Finds all actors that satisfy a predicate.
|
||||
*/
|
||||
def filter(p: ActorRef => Boolean): List[ActorRef] = {
|
||||
def filter(p: ActorRef => Boolean): Array[ActorRef] = {
|
||||
val all = new ListBuffer[ActorRef]
|
||||
val elements = actorsByUUID.elements
|
||||
while (elements.hasMoreElements) {
|
||||
|
|
@ -69,37 +79,34 @@ object ActorRegistry extends ListenerManagement {
|
|||
all += actorId
|
||||
}
|
||||
}
|
||||
all.toList
|
||||
all.toArray
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all actors that are subtypes of the class passed in as the Manifest argument.
|
||||
*/
|
||||
def actorsFor[T <: Actor](implicit manifest: Manifest[T]): List[ActorRef] =
|
||||
filter(a => manifest.erasure.isAssignableFrom(a.actor.getClass))
|
||||
def actorsFor[T <: Actor](implicit manifest: Manifest[T]): Array[ActorRef] =
|
||||
actorsFor[T](manifest.erasure.asInstanceOf[Class[T]])
|
||||
|
||||
/**
|
||||
* Finds any actor that matches T.
|
||||
*/
|
||||
def actorFor[T <: Actor](implicit manifest: Manifest[T]): Option[ActorRef] =
|
||||
actorsFor[T](manifest).headOption
|
||||
find({ case a:ActorRef if manifest.erasure.isAssignableFrom(a.actor.getClass) => a })
|
||||
|
||||
/**
|
||||
* Finds all actors of the exact type specified by the class passed in as the Class argument.
|
||||
* Finds all actors of type or sub-type specified by the class passed in as the Class argument.
|
||||
*/
|
||||
def actorsFor[T <: Actor](clazz: Class[T]): List[ActorRef] = {
|
||||
if (actorsByClassName.containsKey(clazz.getName)) {
|
||||
actorsByClassName.get(clazz.getName).toArray.toList.asInstanceOf[List[ActorRef]]
|
||||
} else Nil
|
||||
}
|
||||
def actorsFor[T <: Actor](clazz: Class[T]): Array[ActorRef] =
|
||||
filter(a => clazz.isAssignableFrom(a.actor.getClass))
|
||||
|
||||
/**
|
||||
* Finds all actors that has a specific id.
|
||||
*/
|
||||
def actorsFor(id: String): List[ActorRef] = {
|
||||
def actorsFor(id: String): Array[ActorRef] = {
|
||||
if (actorsById.containsKey(id)) {
|
||||
actorsById.get(id).toArray.toList.asInstanceOf[List[ActorRef]]
|
||||
} else Nil
|
||||
actorsById.get(id).toArray(Naught)
|
||||
} else Naught
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -114,27 +121,22 @@ object ActorRegistry extends ListenerManagement {
|
|||
* Registers an actor in the ActorRegistry.
|
||||
*/
|
||||
def register(actor: ActorRef) = {
|
||||
// UUID
|
||||
actorsByUUID.put(actor.uuid, actor)
|
||||
|
||||
// ID
|
||||
val id = actor.id
|
||||
if (id eq null) throw new IllegalActorStateException("Actor.id is null " + actor)
|
||||
if (actorsById.containsKey(id)) actorsById.get(id).add(actor)
|
||||
|
||||
val set = actorsById get id
|
||||
if (set ne null) set add actor
|
||||
else {
|
||||
val set = new ConcurrentSkipListSet[ActorRef](refComparator)
|
||||
set.add(actor)
|
||||
actorsById.put(id, set)
|
||||
val newSet = new ConcurrentSkipListSet[ActorRef]
|
||||
newSet add actor
|
||||
val oldSet = actorsById.putIfAbsent(id,newSet)
|
||||
// Parry for two simultaneous putIfAbsent(id,newSet)
|
||||
if (oldSet ne null) oldSet add actor
|
||||
}
|
||||
|
||||
// Class name
|
||||
val className = actor.actorClassName
|
||||
if (actorsByClassName.containsKey(className)) actorsByClassName.get(className).add(actor)
|
||||
else {
|
||||
val set = new ConcurrentSkipListSet[ActorRef](refComparator)
|
||||
set.add(actor)
|
||||
actorsByClassName.put(className, set)
|
||||
}
|
||||
// UUID
|
||||
actorsByUUID.put(actor.uuid, actor)
|
||||
|
||||
// notify listeners
|
||||
foreachListener(_ ! ActorRegistered(actor))
|
||||
|
|
@ -146,11 +148,10 @@ object ActorRegistry extends ListenerManagement {
|
|||
def unregister(actor: ActorRef) = {
|
||||
actorsByUUID remove actor.uuid
|
||||
|
||||
val id = actor.id
|
||||
if (actorsById.containsKey(id)) actorsById.get(id).remove(actor)
|
||||
val set = actorsById get actor.id
|
||||
if (set ne null) set remove actor
|
||||
|
||||
val className = actor.actorClassName
|
||||
if (actorsByClassName.containsKey(className)) actorsByClassName.get(className).remove(actor)
|
||||
//FIXME: safely remove set if empty, leaks memory
|
||||
|
||||
// notify listeners
|
||||
foreachListener(_ ! ActorUnregistered(actor))
|
||||
|
|
@ -159,12 +160,11 @@ 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
|
||||
actorsById.clear
|
||||
actorsByClassName.clear
|
||||
log.info("All actors have been shut down and unregistered from ActorRegistry")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,11 +5,12 @@
|
|||
package se.scalablesolutions.akka.actor
|
||||
|
||||
import se.scalablesolutions.akka.stm.Ref
|
||||
import se.scalablesolutions.akka.AkkaException
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
class AgentException private[akka](message: String) extends RuntimeException(message)
|
||||
class AgentException private[akka](message: String) extends AkkaException(message)
|
||||
|
||||
/**
|
||||
* The Agent class was strongly inspired by the agent principle in Clojure.
|
||||
|
|
|
|||
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