Merge branch 'master' of github.com:akka/akka

Signed-off-by: Jonas Bonér <jonas@jonasboner.com>
This commit is contained in:
Jonas Bonér 2012-03-22 16:24:03 +01:00
commit f7ca01a26b
105 changed files with 3239 additions and 4290 deletions

View file

@ -555,14 +555,16 @@ private[akka] class ActorCell(
def resume(): Unit = if (isNormal) dispatcher resume this
def link(subject: ActorRef): Unit = if (!isTerminating) {
system.deathWatch.subscribe(self, subject)
if (system.deathWatch.subscribe(self, subject)) {
if (system.settings.DebugLifecycle) system.eventStream.publish(Debug(self.path.toString, clazz(actor), "now monitoring " + subject))
}
}
def unlink(subject: ActorRef): Unit = if (!isTerminating) {
system.deathWatch.unsubscribe(self, subject)
if (system.deathWatch.unsubscribe(self, subject)) {
if (system.settings.DebugLifecycle) system.eventStream.publish(Debug(self.path.toString, clazz(actor), "stopped monitoring " + subject))
}
}
def terminate() {
setReceiveTimeout(None)
@ -691,7 +693,7 @@ private[akka] class ActorCell(
parent.sendSystemMessage(ChildTerminated(self))
system.deathWatch.publish(Terminated(self))
if (system.settings.DebugLifecycle)
system.eventStream.publish(Debug(self.path.toString, clazz(actor), "stopped")) // FIXME: can actor be null?
system.eventStream.publish(Debug(self.path.toString, clazz(actor), "stopped"))
} finally {
if (a ne null) a.clearBehaviorStack()
clearActorFields()

View file

@ -383,15 +383,6 @@ private[akka] trait MinimalActorRef extends InternalActorRef with LocalRef {
protected def writeReplace(): AnyRef = SerializedActorRef(path)
}
private[akka] object MinimalActorRef {
def apply(_path: ActorPath, _provider: ActorRefProvider)(receive: PartialFunction[Any, Unit]): ActorRef = new MinimalActorRef {
def path = _path
def provider = _provider
override def !(message: Any)(implicit sender: ActorRef = null): Unit =
if (receive.isDefinedAt(message)) receive(message)
}
}
case class DeadLetter(message: Any, sender: ActorRef, recipient: ActorRef)
private[akka] object DeadLetterActorRef {

View file

@ -1,34 +0,0 @@
/**
* Copyright (C) 2009-2010 Typesafe Inc. <http://www.typesafe.com>
*/
package 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 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();
/**
* Route definition handler class for customizing route to annotated method.
* The handler class must have a default constructor.
*/
public abstract Class<? extends RouteDefinitionHandler> routeDefinitionHandler()
default RouteDefinitionIdentity.class;
}

View file

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

View file

@ -1,55 +0,0 @@
/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camel
import org.apache.camel.CamelContext
import akka.actor.Actor._
import akka.actor._
import akka.camel.component.TypedActorComponent
/**
* Module that adds typed consumer actor support to akka-camel. It is automatically
* detected by CamelService if added to the classpath.
*
* @author Martin Krasser
*/
private[camel] object TypedCamel {
private var consumerPublisher: ActorRef = _
private var publishRequestor: ActorRef = _
/**
* Adds the <code>TypedActorComponent</code> to <code>context</code>.
*/
def onCamelContextInit(context: CamelContext) {
context.addComponent(TypedActorComponent.InternalSchema, new TypedActorComponent)
}
/**
* Configures a <code>TypedConsumerPublishRequestor</code> and a <code>TypedConsumerPublisher</code>
* and re-uses the <code>activationTracker</code> of <code>service</code>.
*/
def onCamelServiceStart(service: CamelService) {
consumerPublisher = new LocalActorRef(Props(new TypedConsumerPublisher(service.activationTracker)), Props.randomName, true)
publishRequestor = new LocalActorRef(Props(new TypedConsumerPublishRequestor), Props.randomName, true)
registerPublishRequestor
for (event PublishRequestor.pastActorRegisteredEvents) publishRequestor ! event
publishRequestor ! InitPublishRequestor(consumerPublisher)
}
/**
* Stops the configured Configures <code>TypedConsumerPublishRequestor</code> and
* <code>TypedConsumerPublisher</code>.
*/
def onCamelServiceStop(service: CamelService) {
unregisterPublishRequestor
consumerPublisher.stop
}
private def registerPublishRequestor: Unit = registry.addListener(publishRequestor)
private def unregisterPublishRequestor: Unit = registry.removeListener(publishRequestor)
}

View file

@ -1,59 +0,0 @@
/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camel
import java.lang.reflect.Method
import java.lang.reflect.Proxy._
import akka.actor.{ LocalActorRef, TypedActor, ActorRef }
import akka.actor.TypedActor._
/**
* @author Martin Krasser
*/
private[camel] object TypedConsumer {
/**
* Applies a function <code>f</code> to <code>actorRef</code> if <code>actorRef</code>
* references a typed consumer actor. A valid reference to a typed consumer actor is a
* local actor reference with a target actor that implements <code>TypedActor</code> and
* has at least one of its methods annotated with <code>@consume</code> (on interface or
* implementation class). For each <code>@consume</code>-annotated method, <code>f</code>
* is called with the corresponding <code>method</code> instance and the return value is
* added to a list which is then returned by this method.
*/
def withTypedConsumer[T](actorRef: ActorRef, typedActor: Option[AnyRef])(f: (AnyRef, Method) T): List[T] = {
typedActor match {
case None Nil
case Some(tc) {
withConsumeAnnotatedMethodsOnInterfaces(tc, f) ++
withConsumeAnnotatedMethodsonImplClass(tc, actorRef, f)
}
}
}
private implicit def class2ProxyClass(c: Class[_]) = new ProxyClass(c)
private def withConsumeAnnotatedMethodsOnInterfaces[T](tc: AnyRef, f: (AnyRef, Method) T): List[T] = for {
i tc.getClass.allInterfaces
m i.getDeclaredMethods.toList
if (m.isAnnotationPresent(classOf[consume]))
} yield f(tc, m)
private def withConsumeAnnotatedMethodsonImplClass[T](tc: AnyRef, actorRef: ActorRef, f: (AnyRef, Method) T): List[T] = actorRef match {
case l: LocalActorRef
val implClass = l.underlyingActorInstance.asInstanceOf[TypedActor.TypedActor[AnyRef, AnyRef]].me.getClass
for (m implClass.getDeclaredMethods.toList; if (m.isAnnotationPresent(classOf[consume]))) yield f(tc, m)
case _ Nil
}
private class ProxyClass(c: Class[_]) {
def allInterfaces: List[Class[_]] = allInterfaces(c.getInterfaces.toList)
def allInterfaces(is: List[Class[_]]): List[Class[_]] = is match {
case Nil Nil
case x :: xs x :: allInterfaces(x.getInterfaces.toList) ::: allInterfaces(xs)
}
}
}

View file

@ -1,139 +0,0 @@
/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camel
import java.lang.reflect.Method
import akka.actor._
import akka.camel.component.TypedActorComponent
import akka.event.EventHandler
/**
* Concrete publish requestor that requests publication of typed consumer actor methods on
* <code>TypedActorRegistered</code> events and unpublication of typed consumer actor methods on
* <code>TypedActorUnregistered</code> events.
*
* @author Martin Krasser
*/
private[camel] class TypedConsumerPublishRequestor extends PublishRequestor {
def receiveActorRegistryEvent = {
case TypedActorRegistered(_, actor, typedActor) for (event ConsumerMethodRegistered.eventsFor(actor, Option(typedActor))) deliverCurrentEvent(event)
case TypedActorUnregistered(_, actor, typedActor) for (event ConsumerMethodUnregistered.eventsFor(actor, Option(typedActor))) deliverCurrentEvent(event)
case _ ()
}
}
/**
* Publishes a typed consumer actor method on <code>ConsumerMethodRegistered</code> events and
* unpublishes a typed consumer actor method on <code>ConsumerMethodUnregistered</code> events.
* Publications are tracked by sending an <code>activationTracker</code> an <code>EndpointActivated</code>
* event, unpublications are tracked by sending an <code>EndpointActivated</code> event.
*
* @author Martin Krasser
*/
private[camel] class TypedConsumerPublisher(activationTracker: ActorRef) extends Actor {
import TypedConsumerPublisher._
def receive = {
case mr: ConsumerMethodRegistered {
handleConsumerMethodRegistered(mr)
activationTracker ! EndpointActivated
}
case mu: ConsumerMethodUnregistered {
handleConsumerMethodUnregistered(mu)
activationTracker ! EndpointDeactivated
}
case _ { /* ignore */ }
}
}
/**
* @author Martin Krasser
*/
private[camel] object TypedConsumerPublisher {
/**
* Creates a route to a typed actor method.
*/
def handleConsumerMethodRegistered(event: ConsumerMethodRegistered) {
CamelContextManager.mandatoryContext.addRoutes(new ConsumerMethodRouteBuilder(event))
EventHandler.info(this, "published method %s of %s at endpoint %s" format (event.methodName, event.typedActor, event.endpointUri))
}
/**
* Stops the route to the already un-registered typed consumer actor method.
*/
def handleConsumerMethodUnregistered(event: ConsumerMethodUnregistered) {
CamelContextManager.mandatoryContext.stopRoute(event.methodUuid)
EventHandler.info(this, "unpublished method %s of %s from endpoint %s" format (event.methodName, event.typedActor, event.endpointUri))
}
}
/**
* Builder of a route to a typed consumer actor method.
*
* @author Martin Krasser
*/
private[camel] class ConsumerMethodRouteBuilder(event: ConsumerMethodRegistered) extends ConsumerRouteBuilder(event.endpointUri, event.methodUuid) {
protected def routeDefinitionHandler: RouteDefinitionHandler = event.routeDefinitionHandler
protected def targetUri = "%s:%s?method=%s" format (TypedActorComponent.InternalSchema, event.methodUuid, event.methodName)
}
/**
* A typed consumer method (un)registration event.
*/
private[camel] trait ConsumerMethodEvent extends ConsumerEvent {
val actorRef: ActorRef
val typedActor: AnyRef
val method: Method
val methodName = method.getName
val methodUuid = "%s_%s" format (uuid, methodName)
lazy val routeDefinitionHandler = consumeAnnotation.routeDefinitionHandler.newInstance
lazy val consumeAnnotation = method.getAnnotation(classOf[consume])
lazy val endpointUri = consumeAnnotation.value
}
/**
* Event indicating that a typed consumer actor has been registered at the actor registry. For
* each <code>@consume</code> annotated typed actor method a separate event is created.
*/
private[camel] case class ConsumerMethodRegistered(actorRef: ActorRef, typedActor: AnyRef, method: Method) extends ConsumerMethodEvent
/**
* Event indicating that a typed consumer actor has been unregistered from the actor registry. For
* each <code>@consume</code> annotated typed actor method a separate event is created.
*/
private[camel] case class ConsumerMethodUnregistered(actorRef: ActorRef, typedActor: AnyRef, method: Method) extends ConsumerMethodEvent
/**
* @author Martin Krasser
*/
private[camel] object ConsumerMethodRegistered {
/**
* Creates a list of ConsumerMethodRegistered event messages for a typed consumer actor or an empty
* list if <code>actorRef</code> doesn't reference a typed consumer actor.
*/
def eventsFor(actorRef: ActorRef, typedActor: Option[AnyRef]): List[ConsumerMethodRegistered] = {
TypedConsumer.withTypedConsumer(actorRef, typedActor) { (tc, m)
ConsumerMethodRegistered(actorRef, tc, m)
}
}
}
/**
* @author Martin Krasser
*/
private[camel] object ConsumerMethodUnregistered {
/**
* Creates a list of ConsumerMethodUnregistered event messages for a typed consumer actor or an empty
* list if <code>actorRef</code> doesn't reference a typed consumer actor.
*/
def eventsFor(actorRef: ActorRef, typedActor: Option[AnyRef]): List[ConsumerMethodUnregistered] = {
TypedConsumer.withTypedConsumer(actorRef, typedActor) { (tc, m)
ConsumerMethodUnregistered(actorRef, tc, m)
}
}
}

View file

@ -1,85 +0,0 @@
/**
* Copyright (C) 2009-2010 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camel.component
import java.util.Map
import java.util.concurrent.ConcurrentHashMap
import org.apache.camel.CamelContext
import org.apache.camel.component.bean._
import akka.actor._
/**
* @author Martin Krasser
*/
object TypedActorComponent {
/**
* Default schema name for typed actor endpoint URIs.
*/
val InternalSchema = "typed-actor-internal"
}
/**
* Camel component for exchanging messages with typed actors. This component
* tries to obtain the typed actor from <code>Actor.registry</code> if the
* schema is <code>TypedActorComponent.InternalSchema</code>. If the schema
* name is <code>typed-actor</code> this component tries to obtain the typed
* actor from the CamelContext's registry.
*
* @see org.apache.camel.component.bean.BeanComponent
*
* @author Martin Krasser
*/
class TypedActorComponent extends BeanComponent {
val typedActorRegistry = new ConcurrentHashMap[String, AnyRef]
/**
* Creates an <code>org.apache.camel.component.bean.BeanEndpoint</code> with a custom
* bean holder that uses <code>Actor.registry</code> for getting access to typed actors
* (beans).
*
* @see akka.camel.component.TypedActorHolder
*/
override def createEndpoint(uri: String, remaining: String, parameters: Map[String, AnyRef]) = {
val endpoint = new BeanEndpoint(uri, this)
endpoint.setBeanName(remaining)
endpoint.setBeanHolder(createBeanHolder(uri, remaining))
setProperties(endpoint.getProcessor, parameters)
endpoint
}
private def createBeanHolder(uri: String, beanName: String) =
new TypedActorHolder(uri, getCamelContext, beanName).createCacheHolder
}
/**
* <code>org.apache.camel.component.bean.BeanHolder</code> implementation that uses
* <code>Actor.registry</code> for getting access to typed actors.
*
* @author Martin Krasser
*/
class TypedActorHolder(uri: String, context: CamelContext, name: String)
extends RegistryBean(context, name) {
/**
* Returns an <code>akka.camel.component.BeanInfo</code> instance.
*/
override def getBeanInfo: BeanInfo =
new BeanInfo(getContext, getBean.getClass, getParameterMappingStrategy)
/**
* Obtains a typed actor from <code>Actor.registry</code> if the schema is
* <code>TypedActorComponent.InternalSchema</code>. If the schema name is
* <code>typed-actor</code> this method obtains the typed actor from the
* CamelContext's registry.
*
* @return a typed actor or <code>null</code>.
*/
override def getBean: AnyRef = {
val internal = uri.startsWith(TypedActorComponent.InternalSchema)
if (internal) Actor.registry.local.typedActorFor(uuidFrom(getName)) getOrElse null else super.getBean
}
}

View file

@ -1,11 +0,0 @@
package akka.camel;
/**
* @author Martin Krasser
*/
public interface SampleErrorHandlingTypedConsumer {
@consume(value="direct:error-handler-test-java-typed", routeDefinitionHandler=SampleRouteDefinitionHandler.class)
String willFail(String s);
}

View file

@ -1,12 +0,0 @@
package akka.camel;
/**
* @author Martin Krasser
*/
public class SampleErrorHandlingTypedConsumerImpl implements SampleErrorHandlingTypedConsumer {
public String willFail(String s) {
throw new RuntimeException(String.format("error: %s", s));
}
}

View file

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

View file

@ -1,12 +0,0 @@
package akka.camel;
/**
* @author Martin Krasser
*/
public class SampleRemoteTypedConsumerImpl implements SampleRemoteTypedConsumer {
public String foo(String s) {
return String.format("remote typed actor: %s", s);
}
}

View file

@ -1,14 +0,0 @@
package akka.camel;
import org.apache.camel.builder.Builder;
import org.apache.camel.model.ProcessorDefinition;
import org.apache.camel.model.RouteDefinition;
/**
* @author Martin Krasser
*/
public class SampleRouteDefinitionHandler implements RouteDefinitionHandler {
public ProcessorDefinition<?> onRouteDefinition(RouteDefinition rd) {
return rd.onException(Exception.class).handled(true).transform(Builder.exceptionMessage()).end();
}
}

View file

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

View file

@ -1,14 +0,0 @@
package akka.camel;
import akka.actor.TypedActor;
/**
* @author Martin Krasser
*/
public class SampleTypedActorImpl implements SampleTypedActor {
public String foo(String s) {
return String.format("foo: %s", s);
}
}

View file

@ -1,20 +0,0 @@
package akka.camel;
import org.apache.camel.Body;
import org.apache.camel.Header;
import 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);
}

View file

@ -1,28 +0,0 @@
package akka.camel;
/**
* @author Martin Krasser
*/
public class SampleTypedConsumerImpl 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) {
}
}

View file

@ -1,13 +0,0 @@
package akka.camel;
import akka.camel.consume;
/**
* @author Martin Krasser
*/
public interface SampleTypedSingleConsumer {
@consume("direct:foo")
public void foo(String b);
}

View file

@ -1,11 +0,0 @@
package akka.camel;
/**
* @author Martin Krasser
*/
public class SampleTypedSingleConsumerImpl implements SampleTypedSingleConsumer {
public void foo(String b) {
}
}

View file

@ -1,52 +0,0 @@
package akka.camel;
import akka.actor.Actor;
import akka.actor.TypedActor;
import akka.actor.Props;
import akka.util.Timeout;
import akka.dispatch.Dispatchers;
import akka.japi.SideEffect;
import akka.util.FiniteDuration;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import static akka.actor.Actors.*;
import static akka.camel.CamelContextManager.*;
import static akka.camel.CamelServiceManager.*;
import static org.junit.Assert.*;
/**
* @author Martin Krasser
*/
public class TypedConsumerJavaTestBase {
private SampleErrorHandlingTypedConsumer consumer;
@BeforeClass
public static void setUpBeforeClass() {
startCamelService();
}
@AfterClass
public static void tearDownAfterClass() {
stopCamelService();
registry().local().shutdownAll();
}
@Test
public void shouldHandleExceptionThrownByTypedActorAndGenerateCustomResponse() {
getMandatoryService().awaitEndpointActivation(1, new SideEffect() {
public void apply() {
consumer = TypedActor.typedActorOf(
SampleErrorHandlingTypedConsumer.class,
SampleErrorHandlingTypedConsumerImpl.class,
(new Props()).withTimeout(new Timeout(new FiniteDuration(5000, "millis"))));
}
});
String result = getMandatoryTemplate().requestBody("direct:error-handler-test-java-typed", "hello", String.class);
assertEquals("error: hello", result);
}
}

View file

@ -1,66 +0,0 @@
package akka.camel
import java.util.concurrent.CountDownLatch
import collection.mutable.Buffer
import akka.actor.Actor
object TypedCamelTestSupport {
type Handler = PartialFunction[Any, Any]
trait TestActor extends Actor {
def receive = {
case msg {
handler(msg)
}
}
def handler: Handler
}
trait Countdown { this: Actor
var latch: CountDownLatch = new CountDownLatch(0)
def countdown: Handler = {
case SetExpectedMessageCount(num) {
latch = new CountDownLatch(num)
sender ! latch
}
case msg latch.countDown
}
}
trait Respond { this: Actor
def respond: Handler = {
case msg: Message sender ! response(msg)
}
def response(msg: Message): Any = "Hello %s" format msg.body
}
trait Retain { this: Actor
val messages = Buffer[Any]()
def retain: Handler = {
case GetRetainedMessage sender ! messages.last
case GetRetainedMessages(p) sender ! messages.filter(p).toList
case msg {
messages += msg
msg
}
}
}
trait Noop { this: Actor
def noop: Handler = {
case msg msg
}
}
case class SetExpectedMessageCount(num: Int)
case class GetRetainedMessage()
case class GetRetainedMessages(p: Any Boolean) {
def this() = this(_ true)
}
}

View file

@ -1,5 +0,0 @@
package akka.camel
import org.scalatest.junit.JUnitSuite
class TypedConsumerJavaTest extends TypedConsumerJavaTestBase with JUnitSuite

View file

@ -1,104 +0,0 @@
package akka.camel
import java.util.concurrent.{ CountDownLatch, TimeUnit }
import org.junit.{ Before, After, Test }
import org.scalatest.junit.JUnitSuite
import akka.util.duration._
import akka.actor._
import akka.actor.Actor._
import akka.camel.TypedCamelTestSupport.{ SetExpectedMessageCount SetExpectedTestMessageCount, _ }
import akka.dispatch.Await
class TypedConsumerPublishRequestorTest extends JUnitSuite {
import TypedConsumerPublishRequestorTest._
var publisher: ActorRef = _
var requestor: ActorRef = _
var consumer: ActorRef = _
val ascendingMethodName = (r1: ConsumerMethodRegistered, r2: ConsumerMethodRegistered)
r1.method.getName < r2.method.getName
@Before
def setUp{
publisher = actorOf(Props(new TypedConsumerPublisherMock)
requestor = actorOf(Props(new TypedConsumerPublishRequestor)
requestor ! InitPublishRequestor(publisher)
consumer = actorOf(Props(new Actor with Consumer {
def endpointUri = "mock:test"
protected def receive = null
})
}
@After
def tearDown = {
Actor.registry.removeListener(requestor);
Actor.registry.local.shutdownAll
}
@Test
def shouldReceiveOneConsumerMethodRegisteredEvent = {
Actor.registry.addListener(requestor)
val latch = Await.result((publisher ? SetExpectedTestMessageCount(1)).mapTo[CountDownLatch], 3 seconds)
val obj = TypedActor.typedActorOf(classOf[SampleTypedSingleConsumer], classOf[SampleTypedSingleConsumerImpl], Props())
assert(latch.await(5000, TimeUnit.MILLISECONDS))
val event = Await.result((publisher ? GetRetainedMessage).mapTo[ConsumerMethodRegistered], 3 seconds)
assert(event.endpointUri === "direct:foo")
assert(event.typedActor === obj)
assert(event.methodName === "foo")
}
@Test
def shouldReceiveOneConsumerMethodUnregisteredEvent = {
val latch = Await.result((publisher ? SetExpectedTestMessageCount(1)).mapTo[CountDownLatch], 3 seconds)
Actor.registry.addListener(requestor)
val obj = TypedActor.typedActorOf(classOf[SampleTypedSingleConsumer], classOf[SampleTypedSingleConsumerImpl], Props())
assert(latch.await(5000, TimeUnit.MILLISECONDS))
val ignorableEvent = Await.result((publisher ? GetRetainedMessage).mapTo[ConsumerMethodRegistered], 3 seconds)
val latch2 = Await.result((publisher ? SetExpectedTestMessageCount(1)).mapTo[CountDownLatch], 3 seconds)
TypedActor.stop(obj)
assert(latch2.await(5000, TimeUnit.MILLISECONDS))
val event = Await.result((publisher ? GetRetainedMessage).mapTo[ConsumerMethodUnregistered], 3 seconds)
assert(event.endpointUri === "direct:foo")
assert(event.typedActor === obj)
assert(event.methodName === "foo")
}
@Test
def shouldReceiveThreeConsumerMethodRegisteredEvents = {
Actor.registry.addListener(requestor)
val latch = Await.result((publisher ? SetExpectedTestMessageCount(3)).mapTo[CountDownLatch], 3 seconds)
val obj = TypedActor.typedActorOf(classOf[SampleTypedConsumer], classOf[SampleTypedConsumerImpl], Props())
assert(latch.await(5000, TimeUnit.MILLISECONDS))
val request = GetRetainedMessages(_.isInstanceOf[ConsumerMethodRegistered])
val events = Await.result((publisher ? request).mapTo[List[ConsumerMethodRegistered]], 3 seconds)
assert(events.map(_.method.getName).sortWith(_ < _) === List("m2", "m3", "m4"))
}
@Test
def shouldReceiveThreeConsumerMethodUnregisteredEvents = {
val obj = TypedActor.typedActorOf(classOf[SampleTypedConsumer], classOf[SampleTypedConsumerImpl], Props())
val latch = Await.result((publisher ? SetExpectedTestMessageCount(3)).mapTo[CountDownLatch], 3 seconds)
Actor.registry.addListener(requestor)
TypedActor.stop(obj)
assert(latch.await(5000, TimeUnit.MILLISECONDS))
val request = GetRetainedMessages(_.isInstanceOf[ConsumerMethodUnregistered])
val events = Await.result((publisher ? request).mapTo[List[ConsumerMethodUnregistered]], 3 seconds)
assert(events.map(_.method.getName).sortWith(_ < _) === List("m2", "m3", "m4"))
}
}
object TypedConsumerPublishRequestorTest {
class TypedConsumerPublisherMock extends TestActor with Retain with Countdown {
def handler = retain andThen countdown
}
}

View file

@ -1,99 +0,0 @@
package akka.camel
import org.apache.camel.CamelExecutionException
import org.scalatest.{ BeforeAndAfterAll, WordSpec }
import org.scalatest.matchers.MustMatchers
import akka.actor.Actor._
import akka.actor._
/**
* @author Martin Krasser
*/
class TypedConsumerScalaTest extends WordSpec with BeforeAndAfterAll with MustMatchers {
import CamelContextManager.mandatoryTemplate
import TypedConsumerScalaTest._
var service: CamelService = _
override protected def beforeAll = {
registry.local.shutdownAll
service = CamelServiceManager.startCamelService
}
override protected def afterAll = {
service.stop
registry.local.shutdownAll
}
"A responding, typed consumer" when {
var actor: SampleTypedConsumer = null
"started" must {
"support in-out message exchanges via its endpoints" in {
service.awaitEndpointActivation(3) {
actor = TypedActor.typedActorOf(classOf[SampleTypedConsumer], classOf[SampleTypedConsumerImpl], Props())
} must be(true)
mandatoryTemplate.requestBodyAndHeader("direct:m2", "x", "test", "y") must equal("m2: x y")
mandatoryTemplate.requestBodyAndHeader("direct:m3", "x", "test", "y") must equal("m3: x y")
mandatoryTemplate.requestBodyAndHeader("direct:m4", "x", "test", "y") must equal("m4: x y")
}
}
"stopped" must {
"not support in-out message exchanges via its endpoints" in {
service.awaitEndpointDeactivation(3) {
TypedActor.stop(actor)
} must be(true)
intercept[CamelExecutionException] {
mandatoryTemplate.requestBodyAndHeader("direct:m2", "x", "test", "y")
}
intercept[CamelExecutionException] {
mandatoryTemplate.requestBodyAndHeader("direct:m3", "x", "test", "y")
}
intercept[CamelExecutionException] {
mandatoryTemplate.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 {
service.awaitEndpointActivation(2) {
actor = TypedActor.typedActorOf(classOf[TestTypedConsumer], classOf[TestTypedConsumerImpl], Props())
} must be(true)
mandatoryTemplate.requestBody("direct:publish-test-3", "x") must equal("foo: x")
mandatoryTemplate.requestBody("direct:publish-test-4", "x") must equal("bar: x")
}
}
"stopped" must {
"not support in-out message exchanges via its endpoints" in {
service.awaitEndpointDeactivation(2) {
TypedActor.stop(actor)
} must be(true)
intercept[CamelExecutionException] {
mandatoryTemplate.requestBody("direct:publish-test-3", "x")
}
intercept[CamelExecutionException] {
mandatoryTemplate.requestBody("direct:publish-test-4", "x")
}
}
}
}
}
object TypedConsumerScalaTest {
trait TestTypedConsumer {
@consume("direct:publish-test-3")
def foo(s: String): String
def bar(s: String): String
}
class TestTypedConsumerImpl extends TestTypedConsumer {
def foo(s: String) = "foo: %s" format s
@consume("direct:publish-test-4")
def bar(s: String) = "bar: %s" format s
}
}

View file

@ -1,113 +0,0 @@
package akka.camel.component
import org.apache.camel._
import org.apache.camel.builder.RouteBuilder
import org.apache.camel.impl.{ DefaultCamelContext, SimpleRegistry }
import org.scalatest.{ BeforeAndAfterEach, BeforeAndAfterAll, FeatureSpec }
import akka.actor.{ Actor, TypedActor, Props }
import akka.camel._
/**
* @author Martin Krasser
*/
class TypedActorComponentFeatureTest extends FeatureSpec with BeforeAndAfterAll with BeforeAndAfterEach {
import TypedActorComponentFeatureTest._
import CamelContextManager.mandatoryTemplate
var typedConsumerUuid: String = _
override protected def beforeAll = {
val typedActor = TypedActor.typedActorOf(
classOf[SampleTypedActor],
classOf[SampleTypedActorImpl], Props()) // not a consumer
val typedConsumer = TypedActor.typedActorOf(
classOf[SampleTypedConsumer],
classOf[SampleTypedConsumerImpl], Props())
typedConsumerUuid = TypedActor.getActorRefFor(typedConsumer).uuid.toString
val registry = new SimpleRegistry
// external registration
registry.put("ta", typedActor)
CamelContextManager.init(new DefaultCamelContext(registry))
CamelContextManager.mandatoryContext.addRoutes(new CustomRouteBuilder)
CamelContextManager.start
}
override protected def afterAll = {
CamelContextManager.stop
Actor.registry.local.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 = mandatoryTemplate.requestBodyAndHeader("%s:%s?method=m2" format (InternalSchema, typedConsumerUuid), "x", "test", "y")
val result2 = mandatoryTemplate.requestBodyAndHeader("%s:%s?method=m4" format (InternalSchema, typedConsumerUuid), "x", "test", "y")
assert(result1 === "m2: x y")
assert(result2 === "m4: x y")
}
scenario("two-way communication with method returning void") {
val result = mandatoryTemplate.requestBodyAndHeader("%s:%s?method=m5" format (InternalSchema, typedConsumerUuid), "x", "test", "y")
assert(result === "x") // returns initial body
}
scenario("one-way communication with method returning String") {
val result = mandatoryTemplate.send("%s:%s?method=m2" format (InternalSchema, typedConsumerUuid), 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 = mandatoryTemplate.send("%s:%s?method=m5" format (InternalSchema, typedConsumerUuid), 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] {
mandatoryTemplate.requestBodyAndHeader("typed-actor:%s?method=m2" format typedConsumerUuid, "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 = mandatoryTemplate.requestBody("typed-actor:ta?method=foo", "test")
assert(result === "foo: test")
}
scenario("two-way communication with method returning String via custom route") {
val result = mandatoryTemplate.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")
}
}
}

View file

@ -1 +0,0 @@
class=akka.camel.component.ActorComponent

View file

@ -0,0 +1,94 @@
/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camel
import internal._
import akka.util.{ Timeout, Duration }
import akka.dispatch.Future
import java.util.concurrent.TimeoutException
import akka.actor.{ ActorSystem, Props, ActorRef }
import akka.pattern._
/**
* Activation trait that can be used to wait on activation or de-activation of Camel endpoints.
* The Camel endpoints are activated asynchronously. This trait can signal when an endpoint is activated or de-activated.
*/
trait Activation {
import akka.dispatch.Await
def system: ActorSystem
private val activationTracker = system.actorOf(Props[ActivationTracker])
/**
* Awaits for endpoint to be activated. It blocks until the endpoint is registered in camel context or timeout expires.
* @param endpoint the endpoint to wait for to be activated
* @param timeout the timeout for the wait
* @throws akka.camel.ActivationTimeoutException if endpoint is not activated within timeout.
*/
def awaitActivation(endpoint: ActorRef, timeout: Duration): ActorRef = {
try {
Await.result(activationFutureFor(endpoint, timeout), timeout)
} catch {
case e: TimeoutException throw new ActivationTimeoutException(endpoint, timeout)
}
}
/**
* Awaits for endpoint to be de-activated. It is blocking until endpoint is unregistered in camel context or timeout expires.
* @param endpoint the endpoint to wait for to be de-activated
* @param timeout the timeout for the wait
* @throws akka.camel.DeActivationTimeoutException if endpoint is not de-activated within timeout.
*/
def awaitDeactivation(endpoint: ActorRef, timeout: Duration) {
try {
Await.result(deactivationFutureFor(endpoint, timeout), timeout)
} catch {
case e: TimeoutException throw new DeActivationTimeoutException(endpoint, timeout)
}
}
/**
* Similar to `awaitActivation` but returns a future instead.
* @param endpoint the endpoint to be activated
* @param timeout the timeout for the Future
*/
def activationFutureFor(endpoint: ActorRef, timeout: Duration): Future[ActorRef] = {
(activationTracker.ask(AwaitActivation(endpoint))(Timeout(timeout))).map[ActorRef] {
case EndpointActivated(_) endpoint
case EndpointFailedToActivate(_, cause) throw cause
}
}
/**
* Similar to awaitDeactivation but returns a future instead.
* @param endpoint the endpoint to be deactivated
* @param timeout the timeout of the Future
*/
def deactivationFutureFor(endpoint: ActorRef, timeout: Duration): Future[Unit] = {
(activationTracker.ask(AwaitDeActivation(endpoint))(Timeout(timeout))).map[Unit] {
case EndpointDeActivated(_) ()
case EndpointFailedToDeActivate(_, cause) throw cause
}
}
}
/**
* An exception for when a timeout has occurred during deactivation of an endpoint
* @param endpoint the endpoint that could not be de-activated in time
* @param timeout the timeout
*/
class DeActivationTimeoutException(endpoint: ActorRef, timeout: Duration) extends TimeoutException {
override def getMessage = "Timed out after %s, while waiting for de-activation of %s" format (timeout, endpoint.path)
}
/**
* An exception for when a timeout has occurred during the activation of an endpoint
* @param endpoint the endpoint that could not be activated in time
* @param timeout the timeout
*/
class ActivationTimeoutException(endpoint: ActorRef, timeout: Duration) extends TimeoutException {
override def getMessage = "Timed out after %s, while waiting for activation of %s" format (timeout, endpoint.path)
}

View file

@ -0,0 +1,15 @@
/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camel
import internal.component.ActorEndpointPath
import akka.actor.ActorRef
import org.apache.camel.model.ProcessorDefinition
class ActorRouteDefinition(definition: ProcessorDefinition[_]) {
def to(actorRef: ActorRef) = definition.to(ActorEndpointPath(actorRef).toCamelPath())
def to(actorRef: ActorRef, consumerConfig: ConsumerConfig) = definition.to(ActorEndpointPath(actorRef).toCamelPath(consumerConfig))
}

View file

@ -0,0 +1,63 @@
/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camel
import internal._
import akka.actor._
import org.apache.camel.{ ProducerTemplate, CamelContext }
//TODO complete this doc
/**
* Camel trait encapsulates the underlying camel machinery.
* '''Note:''' `CamelContext` and `ProducerTemplate` are stopped when the associated actor system is shut down.
*
*/
trait Camel extends ConsumerRegistry with ProducerRegistry with Extension with Activation {
/**
* Underlying camel context.
*
* It can be used to configure camel manually, i.e. when the user wants to add new routes or endpoints,
* i.e. <pre>camel.context.addRoutes(...)</pre>
*
* @see [[org.apache.camel.CamelContext]]
*/
def context: CamelContext
/**
* Producer template.
* @see [[org.apache.camel.ProducerTemplate]]
*/
def template: ProducerTemplate
}
/**
* This class can be used to get hold of an instance of the Camel class bound to the actor system.
* <p>For example:
* {{{
* val system = ActorSystem("some system")
* val camel = CamelExtension(system)
* camel.context.addRoutes(...)
* }}}
*
* @see akka.actor.ExtensionId
* @see akka.actor.ExtensionIdProvider
*
*/
object CamelExtension extends ExtensionId[Camel] with ExtensionIdProvider {
/**
* Creates a new instance of Camel and makes sure it gets stopped when the actor system is shutdown.
*/
def createExtension(system: ExtendedActorSystem) = {
val camel = new DefaultCamel(system).start
system.registerOnTermination(camel.shutdown())
camel
}
def lookup(): ExtensionId[Camel] = CamelExtension
override def get(system: ActorSystem): Camel = super.get(system)
}

View file

@ -1,186 +0,0 @@
/**
* Copyright (C) 2009-2010 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camel
import org.apache.camel.{ ProducerTemplate, CamelContext }
import org.apache.camel.impl.DefaultCamelContext
import akka.event.EventHandler
import akka.japi.{ Option JOption }
import TypedCamelAccess._
/**
* Manages the lifecycle of a CamelContext. Allowed transitions are
* init -> start -> stop -> init -> ... etc.
*
* @author Martin Krasser
*/
trait CamelContextLifecycle {
// TODO: enforce correct state transitions
// valid: init -> start -> stop -> init ...
private var _context: Option[CamelContext] = None
private var _template: Option[ProducerTemplate] = None
private var _initialized = false
private var _started = false
/**
* Returns <code>Some(CamelContext)</code> (containing the current CamelContext)
* if <code>CamelContextLifecycle</code> has been initialized, otherwise <code>None</code>.
*/
def context: Option[CamelContext] = _context
/**
* Returns <code>Some(ProducerTemplate)</code> (containing the current ProducerTemplate)
* if <code>CamelContextLifecycle</code> has been initialized, otherwise <code>None</code>.
*/
def template: Option[ProducerTemplate] = _template
/**
* Returns <code>Some(CamelContext)</code> (containing the current CamelContext)
* if <code>CamelContextLifecycle</code> has been initialized, otherwise <code>None</code>.
* <p>
* Java API.
*/
def getContext: JOption[CamelContext] = context
/**
* Returns <code>Some(ProducerTemplate)</code> (containing the current ProducerTemplate)
* if <code>CamelContextLifecycle</code> has been initialized, otherwise <code>None</code>.
* <p>
* Java API.
*/
def getTemplate: JOption[ProducerTemplate] = template
/**
* Returns the current <code>CamelContext</code> if this <code>CamelContextLifecycle</code>
* has been initialized, otherwise throws an <code>IllegalStateException</code>.
*/
def mandatoryContext =
if (context.isDefined) context.get
else throw new IllegalStateException("no current CamelContext")
/**
* Returns the current <code>ProducerTemplate</code> if this <code>CamelContextLifecycle</code>
* has been initialized, otherwise throws an <code>IllegalStateException</code>.
*/
def mandatoryTemplate =
if (template.isDefined) template.get
else throw new IllegalStateException("no current ProducerTemplate")
/**
* Returns the current <code>CamelContext</code> if this <code>CamelContextLifecycle</code>
* has been initialized, otherwise throws an <code>IllegalStateException</code>.
* <p>
* Java API.
*/
def getMandatoryContext = mandatoryContext
/**
* Returns the current <code>ProducerTemplate</code> if this <code>CamelContextLifecycle</code>
* has been initialized, otherwise throws an <code>IllegalStateException</code>.
* <p>
* Java API.
*/
def getMandatoryTemplate = mandatoryTemplate
def initialized = _initialized
def started = _started
/**
* Starts the CamelContext and an associated ProducerTemplate.
*/
def start = {
for {
c context
t template
} {
c.start()
t.start()
_started = true
EventHandler.info(this, "Camel context started")
}
}
/**
* Stops the CamelContext and the associated ProducerTemplate.
*/
def stop = {
for {
t template
c context
} {
t.stop
c.stop
_started = false
_initialized = false
EventHandler.info(this, "Camel context stopped")
}
}
/**
* Initializes this instance a new DefaultCamelContext.
*/
def init(): Unit = init(new DefaultCamelContext)
/**
* Initializes this instance with the given CamelContext. For the passed <code>context</code>
* stream-caching is enabled. If applications want to disable stream-caching they can do so
* after this method returned and prior to calling start.
*/
def init(context: CamelContext) {
context.setStreamCaching(true)
for (tc TypedCamelModule.typedCamelObject) tc.onCamelContextInit(context)
this._context = Some(context)
this._template = Some(context.createProducerTemplate)
_initialized = true
EventHandler.info(this, "Camel context initialized")
}
}
/**
* Manages a global CamelContext and an associated ProducerTemplate.
*/
object CamelContextManager extends CamelContextLifecycle {
// -----------------------------------------------------
// The inherited getters aren't statically accessible
// from Java. Therefore, they are redefined here.
// TODO: investigate if this is a Scala bug.
// -----------------------------------------------------
/**
* see CamelContextLifecycle.getContext
* <p>
* Java API.
*/
override def getContext: JOption[CamelContext] = super.getContext
/**
* see CamelContextLifecycle.getTemplate
* <p>
* Java API.
*/
override def getTemplate: JOption[ProducerTemplate] = super.getTemplate
/**
* see CamelContextLifecycle.getMandatoryContext
* <p>
* Java API.
*/
override def getMandatoryContext = super.getMandatoryContext
/**
* see CamelContextLifecycle.getMandatoryTemplate
* <p>
* Java API.
*/
override def getMandatoryTemplate = super.getMandatoryTemplate
}

View file

@ -0,0 +1,266 @@
/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camel
import java.util.{ Map JMap, Set JSet }
import scala.collection.JavaConversions._
import akka.japi.{ Function JFunction }
import org.apache.camel.{ CamelContext, Message JCamelMessage }
/**
* An immutable representation of a Camel message.
*
* @author Martin Krasser
*/
case class CamelMessage(body: Any, headers: Map[String, Any]) {
def this(body: Any, headers: JMap[String, Any]) = this(body, headers.toMap) //for Java
override def toString = "CamelMessage(%s, %s)" format (body, headers)
/**
* Returns those headers from this message whose name is contained in <code>names</code>.
*/
def headers(names: Set[String]): Map[String, Any] = headers.filterKeys(names contains _)
/**
* Returns those headers from this message whose name is contained in <code>names</code>.
* The returned headers map is backed up by an immutable headers map. Any attempt to modify
* the returned map will throw an exception.
* <p>
* Java API
*/
def getHeaders(names: JSet[String]): JMap[String, Any] = headers(names.toSet)
/**
* Returns all headers from this message. The returned headers map is backed up by this
* message's immutable headers map. Any attempt to modify the returned map will throw an
* exception.
* <p>
* Java API
*/
def getHeaders: JMap[String, Any] = headers
/**
* Returns the header with given <code>name</code>. Throws <code>NoSuchElementException</code>
* if the header doesn't exist.
*/
def header(name: String): Option[Any] = headers.get(name)
/**
* Returns the header with given <code>name</code>. Throws <code>NoSuchElementException</code>
* if the header doesn't exist.
* <p>
* Java API
*/
def getHeader(name: String): Any = headers(name)
/**
* Creates a CamelMessage with a transformed body using a <code>transformer</code> function.
*/
def mapBody[A, B](transformer: A B): CamelMessage = withBody(transformer(body.asInstanceOf[A]))
/**
* Creates a CamelMessage with a transformed body using a <code>transformer</code> function.
* <p>
* Java API
*/
def mapBody[A, B](transformer: JFunction[A, B]): CamelMessage = withBody(transformer(body.asInstanceOf[A]))
/**
* Creates a CamelMessage with a given <code>body</code>.
*/
def withBody(body: Any) = CamelMessage(body, this.headers)
/**
* Creates a new CamelMessage with given <code>headers</code>.
*/
def withHeaders[A](headers: Map[String, A]): CamelMessage = copy(this.body, headers)
/**
* Creates a new CamelMessage with given <code>headers</code>. A copy of the headers map is made.
* <p>
* Java API
*/
def withHeaders[A](headers: JMap[String, A]): CamelMessage = withHeaders(headers.toMap)
/**
* Creates a new CamelMessage with given <code>headers</code> added to the current headers.
*/
def addHeaders[A](headers: Map[String, A]): CamelMessage = copy(this.body, this.headers ++ headers)
/**
* Creates a new CamelMessage with given <code>headers</code> added to the current headers.
* A copy of the headers map is made.
* <p>
* Java API
*/
def addHeaders[A](headers: JMap[String, A]): CamelMessage = addHeaders(headers.toMap)
/**
* Creates a new CamelMessage with the given <code>header</code> added to the current headers.
*/
def addHeader(header: (String, Any)): CamelMessage = copy(this.body, this.headers + header)
/**
* Creates a new CamelMessage with the given header, represented by <code>name</code> and
* <code>value</code> added to the existing headers.
* <p>
* Java API
*/
def addHeader(name: String, value: Any): CamelMessage = addHeader((name, value))
/**
* Creates a new CamelMessage where the header with given <code>headerName</code> is removed from
* the existing headers.
*/
def withoutHeader(headerName: String) = copy(this.body, this.headers - headerName)
def copyContentTo(to: JCamelMessage) = {
to.setBody(this.body)
for ((name, value) this.headers) to.getHeaders.put(name, value.asInstanceOf[AnyRef])
}
/**
* Returns the body of the message converted to the type <code>T</code>. Conversion is done
* using Camel's type converter. The type converter is obtained from the CamelContext managed
* by CamelContextManager. Applications have to ensure proper initialization of
* CamelContextManager.
*
* @see CamelContextManager.
*/
def bodyAs[T](implicit m: Manifest[T], camelContext: CamelContext): T = getBodyAs(m.erasure.asInstanceOf[Class[T]], camelContext)
/**
* Returns the body of the message converted to the type as given by the <code>clazz</code>
* parameter. Conversion is done using Camel's type converter. The type converter is obtained
* from the CamelContext managed by CamelContextManager. Applications have to ensure proper
* initialization of CamelContextManager.
* <p>
* Java API
*
* @see CamelContextManager.
*/
def getBodyAs[T](clazz: Class[T], camelContext: CamelContext): T =
camelContext.getTypeConverter.mandatoryConvertTo[T](clazz, body)
/**
* Creates a CamelMessage with current <code>body</code> converted to type <code>T</code>.
*/
def withBodyAs[T](implicit m: Manifest[T], camelContext: CamelContext): CamelMessage = withBodyAs(m.erasure.asInstanceOf[Class[T]])
/**
* Creates a CamelMessage with current <code>body</code> converted to type <code>clazz</code>.
* <p>
* Java API
*/
def withBodyAs[T](clazz: Class[T])(implicit camelContext: CamelContext): CamelMessage = withBody(getBodyAs(clazz, camelContext))
/**
* 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], camelContext: CamelContext): Option[T] = header(name).map(camelContext.getTypeConverter.mandatoryConvertTo[T](m.erasure.asInstanceOf[Class[T]], _))
/**
* Returns the header with given <code>name</code> converted to type as given by the <code>clazz</code>
* parameter. Throws <code>NoSuchElementException</code> if the header doesn't exist.
* <p>
* Java API
*/
def getHeaderAs[T](name: String, clazz: Class[T], camelContext: CamelContext) = headerAs[T](name)(Manifest.classType(clazz), camelContext).get
}
/**
* Companion object of CamelMessage class.
*
* @author Martin Krasser
*/
object CamelMessage {
/**
* CamelMessage header to correlate request with response messages. Applications that send
* messages to a Producer actor may want to set this header on the request message
* so that it can be correlated with an asynchronous response. Messages send to Consumer
* actors have this header already set.
*/
val MessageExchangeId = "MessageExchangeId".intern
/**
* Creates a canonical form of the given message <code>msg</code>. If <code>msg</code> of type
* CamelMessage then <code>msg</code> is returned, otherwise <code>msg</code> is set as body of a
* newly created CamelMessage object.
*/
def canonicalize(msg: Any) = msg match {
case mobj: CamelMessage mobj
case body CamelMessage(body, Map.empty)
}
/**
* Creates a new CamelMessage object from the Camel message.
*/
def from(camelMessage: JCamelMessage): CamelMessage = from(camelMessage, Map.empty)
/**
* Creates a new CamelMessage object from the Camel message.
*
* @param headers additional headers to set on the created CamelMessage in addition to those
* in the Camel message.
*/
def from(camelMessage: JCamelMessage, headers: Map[String, Any]): CamelMessage = CamelMessage(camelMessage.getBody, headers ++ camelMessage.getHeaders)
}
/**
* Positive acknowledgement message (used for application-acknowledged message receipts).
*
* @author Martin Krasser
*/
case object Ack {
/** Java API to get the Ack singleton */
def ack = this
}
/**
* An immutable representation of a failed Camel exchange. It contains the failure cause
* obtained from Exchange.getException and the headers from either the Exchange.getIn
* message or Exchange.getOut message, depending on the exchange pattern.
*
* @author Martin Krasser
*/
case class Failure(val cause: Throwable, val headers: Map[String, Any] = Map.empty) {
/**
* Creates a Failure with cause body and empty headers map.
*/
def this(cause: Throwable) = this(cause, Map.empty[String, Any])
/**
* Creates a Failure with given cause and headers map. A copy of the headers map is made.
* <p>
* Java API
*/
def this(cause: Throwable, headers: JMap[String, Any]) = this(cause, headers.toMap)
/**
* Returns the cause of this Failure.
* <p>
* Java API.
*/
def getCause = cause
/**
* Returns all headers from this failure message. The returned headers map is backed up by
* this message's immutable headers map. Any attempt to modify the returned map will throw
* an exception.
* <p>
* Java API
*/
def getHeaders: JMap[String, Any] = headers
}

View file

@ -1,268 +0,0 @@
/**
* Copyright (C) 2009-2010 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camel
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import org.apache.camel.CamelContext
import akka.actor.{ newUuid, Props, LocalActorRef, Actor }
import akka.config.Config._
import akka.japi.{ SideEffect, Option JOption }
import akka.util.Bootable
import TypedCamelAccess._
import akka.dispatch.Await
/**
* Publishes consumer actors at their Camel endpoints. Consumer actors are published asynchronously when
* they are started and un-published asynchronously when they are stopped. The CamelService is notified
* about actor life cycle by registering listeners at Actor.registry.
* <p>
* If the akka-camel-typed jar is on the classpath, it is automatically detected by the CamelService. The
* optional akka-camel-typed jar provides support for typed consumer actors.
*
* @author Martin Krasser
*/
trait CamelService extends Bootable {
private[camel] val activationTracker = new LocalActorRef(Props[ActivationTracker], Props.randomName, true)
private[camel] val consumerPublisher = new LocalActorRef(Props(new ConsumerPublisher(activationTracker)), Props.randomName, true)
private[camel] val publishRequestor = new LocalActorRef(Props(new ConsumerPublishRequestor), Props.randomName, true)
private val serviceEnabled = config.getList("akka.enabled-modules").exists(_ == "camel")
/**
* Starts this CamelService if the <code>akka.enabled-modules</code> list contains <code>"camel"</code>.
*/
abstract override def onLoad = {
super.onLoad
if (serviceEnabled) start
}
/**
* Stops this CamelService if the <code>akka.enabled-modules</code> list contains <code>"camel"</code>.
*/
abstract override def onUnload = {
if (serviceEnabled) stop
super.onUnload
}
@deprecated("use start() instead", "1.1")
def load = start
@deprecated("use stop() instead", "1.1")
def unload = stop
/**
* Starts this CamelService.
*/
def start: CamelService = {
// Only init and start if not already done by system
if (!CamelContextManager.initialized) CamelContextManager.init
if (!CamelContextManager.started) CamelContextManager.start
registerPublishRequestor
// send registration events for all (un)typed actors that have been registered in the past.
for (event PublishRequestor.pastActorRegisteredEvents) publishRequestor ! event
// init publishRequestor so that buffered and future events are delivered to consumerPublisher
publishRequestor ! InitPublishRequestor(consumerPublisher)
for (tc TypedCamelModule.typedCamelObject) tc.onCamelServiceStart(this)
// Register this instance as current CamelService and return it
CamelServiceManager.register(this)
CamelServiceManager.mandatoryService
}
/**
* Stops this CamelService.
*/
def stop = {
// Unregister this instance as current CamelService
CamelServiceManager.unregister(this)
for (tc TypedCamelModule.typedCamelObject) tc.onCamelServiceStop(this)
// Remove related listeners from registry
unregisterPublishRequestor
// Stop related services
consumerPublisher.stop
activationTracker.stop
CamelContextManager.stop
}
/**
* Waits for an expected number (<code>count</code>) of consumer actor endpoints to be activated
* during execution of <code>f</code>. The wait-timeout is by default 10 seconds. Other timeout
* values can be set via the <code>timeout</code> and <code>timeUnit</code> parameters.
*/
def awaitEndpointActivation(count: Int, timeout: Long = 10, timeUnit: TimeUnit = TimeUnit.SECONDS)(f: Unit): Boolean = {
val activation = expectEndpointActivationCount(count)
f; activation.await(timeout, timeUnit)
}
/**
* Waits for an expected number (<code>count</code>) of consumer actor endpoints to be de-activated
* during execution of <code>f</code>. The wait-timeout is by default 10 seconds. Other timeout
* values can be set via the <code>timeout</code> and <code>timeUnit</code>
* parameters.
*/
def awaitEndpointDeactivation(count: Int, timeout: Long = 10, timeUnit: TimeUnit = TimeUnit.SECONDS)(f: Unit): Boolean = {
val activation = expectEndpointDeactivationCount(count)
f; activation.await(timeout, timeUnit)
}
/**
* Waits for an expected number (<code>count</code>) of consumer actor endpoints to be activated
* during execution of <code>p</code>. The wait timeout is 10 seconds.
* <p>
* Java API
*/
def awaitEndpointActivation(count: Int, p: SideEffect): Boolean = {
awaitEndpointActivation(count, 10, TimeUnit.SECONDS, p)
}
/**
* Waits for an expected number (<code>count</code>) of consumer actor endpoints to be activated
* during execution of <code>p</code>. Timeout values can be set via the
* <code>timeout</code> and <code>timeUnit</code> parameters.
* <p>
* Java API
*/
def awaitEndpointActivation(count: Int, timeout: Long, timeUnit: TimeUnit, p: SideEffect): Boolean = {
awaitEndpointActivation(count, timeout, timeUnit) { p.apply }
}
/**
* Waits for an expected number (<code>count</code>) of consumer actor endpoints to be de-activated
* during execution of <code>p</code>. The wait timeout is 10 seconds.
* <p>
* Java API
*/
def awaitEndpointDeactivation(count: Int, p: SideEffect): Boolean = {
awaitEndpointDeactivation(count, 10, TimeUnit.SECONDS, p)
}
/**
* Waits for an expected number (<code>count</code>) of consumer actor endpoints to be de-activated
* during execution of <code>p</code>. Timeout values can be set via the
* <code>timeout</code> and <code>timeUnit</code> parameters.
* <p>
* Java API
*/
def awaitEndpointDeactivation(count: Int, timeout: Long, timeUnit: TimeUnit, p: SideEffect): Boolean = {
awaitEndpointDeactivation(count, timeout, timeUnit) { p.apply }
}
/**
* Sets an expectation on the number of upcoming endpoint activations and returns
* a CountDownLatch that can be used to wait for the activations to occur. Endpoint
* activations that occurred in the past are not considered.
*/
private def expectEndpointActivationCount(count: Int): CountDownLatch =
Await.result((activationTracker ? SetExpectedActivationCount(count)).mapTo[CountDownLatch], 3 seconds)
/**
* Sets an expectation on the number of upcoming endpoint de-activations and returns
* a CountDownLatch that can be used to wait for the de-activations to occur. Endpoint
* de-activations that occurred in the past are not considered.
*/
private def expectEndpointDeactivationCount(count: Int): CountDownLatch =
Await.result((activationTracker ? SetExpectedDeactivationCount(count)).mapTo[CountDownLatch], 3 seconds)
private[camel] def registerPublishRequestor: Unit =
Actor.registry.addListener(publishRequestor)
private[camel] def unregisterPublishRequestor: Unit =
Actor.registry.removeListener(publishRequestor)
}
/**
* Manages a CamelService (the 'current' CamelService).
*
* @author Martin Krasser
*/
object CamelServiceManager {
/**
* The current CamelService which is defined when a CamelService has been started.
*/
private var _current: Option[CamelService] = None
/**
* Starts a new CamelService, makes it the current CamelService and returns it.
*
* @see CamelService#start
* @see CamelService#onLoad
*/
def startCamelService = CamelServiceFactory.createCamelService.start
/**
* Stops the current CamelService (if defined).
*
* @see CamelService#stop
* @see CamelService#onUnload
*/
def stopCamelService = for (s service) s.stop
/**
* Returns <code>Some(CamelService)</code> if this <code>CamelService</code>
* has been started, <code>None</code> otherwise.
*/
def service = _current
/**
* Returns the current <code>CamelService</code> if <code>CamelService</code>
* has been started, otherwise throws an <code>IllegalStateException</code>.
* <p>
* Java API
*/
def getService: JOption[CamelService] = CamelServiceManager.service
/**
* Returns <code>Some(CamelService)</code> (containing the current CamelService)
* if this <code>CamelService</code>has been started, <code>None</code> otherwise.
*/
def mandatoryService =
if (_current.isDefined) _current.get
else throw new IllegalStateException("co current CamelService")
/**
* Returns <code>Some(CamelService)</code> (containing the current CamelService)
* if this <code>CamelService</code>has been started, <code>None</code> otherwise.
* <p>
* Java API
*/
def getMandatoryService = mandatoryService
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")
}
/**
* @author Martin Krasser
*/
object CamelServiceFactory {
/**
* Creates a new CamelService instance.
*/
def createCamelService: CamelService = new CamelService {}
/**
* Creates a new CamelService instance and initializes it with the given CamelContext.
*/
def createCamelService(camelContext: CamelContext): CamelService = {
CamelContextManager.init(camelContext)
createCamelService
}
}

View file

@ -1,144 +1,67 @@
/**
* Copyright (C) 2009-2010 Typesafe Inc. <http://www.typesafe.com>
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camel
import internal.component.DurationTypeConverter
import org.apache.camel.model.{ RouteDefinition, ProcessorDefinition }
import akka.actor._
import akka.util.Duration
import akka.util.duration._
/**
* Mixed in by Actor implementations that consume message from Camel endpoints.
*
* @author Martin Krasser
*/
trait Consumer { this: Actor
import RouteDefinitionHandler._
trait Consumer extends Actor with ConsumerConfig {
/**
* The default route definition handler is the identity function
*/
private[camel] var routeDefinitionHandler: RouteDefinitionHandler = identity
/**
* Returns the Camel endpoint URI to consume messages from.
*/
def endpointUri: String
protected[this] implicit def camel = CamelExtension(context.system)
protected[this] implicit def camelContext = camel.context
camel.registerConsumer(endpointUri, this, activationTimeout)
}
/**
* Determines whether two-way communications between an endpoint and this consumer actor
* should be done in blocking or non-blocking mode (default is non-blocking). This method
* doesn't have any effect on one-way communications (they'll never block).
* For internal use only.
*/
def blocking = false
private[camel] object DefaultConsumerConfig extends ConsumerConfig
trait ConsumerConfig {
/**
* How long the actor should wait for activation before it fails.
*/
def activationTimeout: Duration = 10 seconds
/**
* When endpoint is out-capable (can produce responses) replyTimeout is the maximum time
* the endpoint can take to send the response before the message exchange fails. It defaults to 1 minute.
* This setting is used for out-capable, in-only, manually acknowledged communication.
* When the blocking is set to Blocking replyTimeout is ignored.
*/
def replyTimeout: Duration = 1 minute
/**
* Determines whether one-way communications between an endpoint and this consumer actor
* should be auto-acknowledged or system-acknowledged.
* should be auto-acknowledged or application-acknowledged.
* This flag has only effect when exchange is in-only.
*/
def autoack = true
def autoack: Boolean = true
/**
* Sets the route definition handler for creating a custom route to this consumer instance.
* The route definition handler for creating a custom route to this consumer instance.
*/
def onRouteDefinition(h: RouteDefinition ProcessorDefinition[_]): Unit = onRouteDefinition(from(h))
//FIXME: write a test confirming onRouteDefinition gets called
def onRouteDefinition(rd: RouteDefinition): ProcessorDefinition[_] = rd
/**
* Sets the route definition handler for creating a custom route to this consumer instance.
* <p>
* Java API.
* For internal use only. Converts this ConsumerConfig to camel URI parameters
* @return
*/
def onRouteDefinition(h: RouteDefinitionHandler): Unit = routeDefinitionHandler = h
}
/**
* Java-friendly Consumer.
*
* Subclass this abstract class to create an MDB-style untyped consumer actor. This
* class is meant to be used from Java.
*
* @author Martin Krasser
*/
abstract class UntypedConsumerActor extends UntypedActor with Consumer {
final override def endpointUri = getEndpointUri
final override def blocking = isBlocking
final override def autoack = isAutoack
/**
* Returns the Camel endpoint URI to consume messages from.
*/
def getEndpointUri(): String
/**
* Determines whether two-way communications between an endpoint and this consumer actor
* should be done in blocking or non-blocking mode (default is non-blocking). This method
* doesn't have any effect on one-way communications (they'll never block).
*/
def isBlocking() = super.blocking
/**
* Determines whether one-way communications between an endpoint and this consumer actor
* should be auto-acknowledged or system-acknowledged.
*/
def isAutoack() = super.autoack
}
/**
* A callback handler for route definitions to consumer actors.
*
* @author Martin Krasser
*/
trait RouteDefinitionHandler {
def onRouteDefinition(rd: RouteDefinition): ProcessorDefinition[_]
}
/**
* The identity route definition handler.
*
* @author Martin Krasser
*
*/
class RouteDefinitionIdentity extends RouteDefinitionHandler {
def onRouteDefinition(rd: RouteDefinition) = rd
}
/**
* @author Martin Krasser
*/
object RouteDefinitionHandler {
/**
* Returns the identity route definition handler
*/
val identity = new RouteDefinitionIdentity
/**
* Created a route definition handler from the given function.
*/
def from(f: RouteDefinition ProcessorDefinition[_]) = new RouteDefinitionHandler {
def onRouteDefinition(rd: RouteDefinition) = f(rd)
}
}
/**
* @author Martin Krasser
*/
private[camel] object Consumer {
/**
* Applies a function <code>f</code> to <code>actorRef</code> if <code>actorRef</code>
* references a consumer actor. A valid reference to a consumer actor is a local actor
* reference with a target actor that implements the <code>Consumer</code> trait. The
* target <code>Consumer</code> instance is passed as argument to <code>f</code>. This
* method returns <code>None</code> if <code>actorRef</code> is not a valid reference
* to a consumer actor, <code>Some</code> contained the return value of <code>f</code>
* otherwise.
*/
def withConsumer[T](actorRef: ActorRef)(f: Consumer T): Option[T] = actorRef match {
case l: LocalActorRef
l.underlyingActorInstance match {
case c: Consumer Some(f(c))
case _ None
}
case _ None
}
private[camel] def toCamelParameters: String = "autoack=%s&replyTimeout=%s" format (autoack, DurationTypeConverter.toString(replyTimeout))
}

View file

@ -1,221 +0,0 @@
/**
* Copyright (C) 2009-2010 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camel
import java.io.InputStream
import java.util.concurrent.CountDownLatch
import org.apache.camel.builder.RouteBuilder
import org.apache.camel.model.RouteDefinition
import akka.actor._
import akka.event.EventHandler
/**
* Concrete publish requestor that requests publication of consumer actors on <code>ActorRegistered</code>
* events and unpublication of consumer actors on <code>ActorUnregistered</code> events.
*
* @author Martin Krasser
*/
private[camel] class ConsumerPublishRequestor extends PublishRequestor {
def receiveActorRegistryEvent = {
case ActorRegistered(_, actor) for (event ConsumerActorRegistered.eventFor(actor)) deliverCurrentEvent(event)
case ActorUnregistered(_, actor) for (event ConsumerActorUnregistered.eventFor(actor)) deliverCurrentEvent(event)
case _ ()
}
}
/**
* Publishes consumer actors on <code>ConsumerActorRegistered</code> events and unpublishes
* consumer actors on <code>ConsumerActorUnregistered</code> events. Publications are tracked
* by sending an <code>activationTracker</code> an <code>EndpointActivated</code> event,
* unpublications are tracked by sending an <code>EndpointActivated</code> event.
*
* @author Martin Krasser
*/
private[camel] class ConsumerPublisher(activationTracker: ActorRef) extends Actor {
import ConsumerPublisher._
def receive = {
case r: ConsumerActorRegistered {
handleConsumerActorRegistered(r)
activationTracker ! EndpointActivated
}
case u: ConsumerActorUnregistered {
handleConsumerActorUnregistered(u)
activationTracker ! EndpointDeactivated
}
case _ { /* ignore */ }
}
}
/**
* @author Martin Krasser
*/
private[camel] object ConsumerPublisher {
/**
* Creates a route to the registered consumer actor.
*/
def handleConsumerActorRegistered(event: ConsumerActorRegistered) {
CamelContextManager.mandatoryContext.addRoutes(new ConsumerActorRouteBuilder(event))
EventHandler notifyListeners EventHandler.Info(this, "published actor %s at endpoint %s" format (event.actorRef, event.endpointUri))
}
/**
* Stops the route to the already un-registered consumer actor.
*/
def handleConsumerActorUnregistered(event: ConsumerActorUnregistered) {
CamelContextManager.mandatoryContext.stopRoute(event.uuid)
EventHandler notifyListeners EventHandler.Info(this, "unpublished actor %s from endpoint %s" format (event.actorRef, event.endpointUri))
}
}
/**
* Abstract builder of a route to a target which can be either an actor or an typed actor method.
*
* @param endpointUri endpoint URI of the consumer actor or typed actor method.
* @param id unique route identifier.
*
* @author Martin Krasser
*/
private[camel] abstract class ConsumerRouteBuilder(endpointUri: String, id: String) extends RouteBuilder {
// TODO: make conversions configurable
private val bodyConversions = Map(
"file" -> classOf[InputStream])
def configure = {
val schema = endpointUri take endpointUri.indexOf(":") // e.g. "http" from "http://whatever/..."
val cnvopt = bodyConversions.get(schema)
onRouteDefinition(startRouteDefinition(cnvopt)).to(targetUri)
}
protected def routeDefinitionHandler: RouteDefinitionHandler
protected def targetUri: String
private def onRouteDefinition(rd: RouteDefinition) = routeDefinitionHandler.onRouteDefinition(rd)
private def startRouteDefinition(bodyConversion: Option[Class[_]]): RouteDefinition = bodyConversion match {
case Some(clazz) from(endpointUri).routeId(id).convertBodyTo(clazz)
case None from(endpointUri).routeId(id)
}
}
/**
* Builder of a route to a consumer actor.
*
* @author Martin Krasser
*/
private[camel] class ConsumerActorRouteBuilder(event: ConsumerActorRegistered) extends ConsumerRouteBuilder(event.endpointUri, event.uuid) {
protected def routeDefinitionHandler: RouteDefinitionHandler = event.routeDefinitionHandler
protected def targetUri = "actor:uuid:%s?blocking=%s&autoack=%s" format (event.uuid, event.blocking, event.autoack)
}
/**
* Tracks <code>EndpointActivated</code> and <code>EndpointDectivated</code> events. Used to wait for a
* certain number of endpoints activations and de-activations to occur.
*
* @see SetExpectedActivationCount
* @see SetExpectedDeactivationCount
*
* @author Martin Krasser
*/
private[camel] class ActivationTracker extends Actor {
private var activationLatch = new CountDownLatch(0)
private var deactivationLatch = new CountDownLatch(0)
def receive = {
case SetExpectedActivationCount(num) {
activationLatch = new CountDownLatch(num)
sender ! activationLatch
}
case SetExpectedDeactivationCount(num) {
deactivationLatch = new CountDownLatch(num)
sender ! deactivationLatch
}
case EndpointActivated activationLatch.countDown
case EndpointDeactivated deactivationLatch.countDown
}
}
/**
* Command message that sets the number of expected endpoint activations on <code>ActivationTracker</code>.
*/
private[camel] case class SetExpectedActivationCount(num: Int)
/**
* Command message that sets the number of expected endpoint de-activations on <code>ActivationTracker</code>.
*/
private[camel] case class SetExpectedDeactivationCount(num: Int)
/**
* Event message indicating that a single endpoint has been activated.
*/
private[camel] case class EndpointActivated()
/**
* Event message indicating that a single endpoint has been de-activated.
*/
private[camel] case class EndpointDeactivated()
/**
* A consumer (un)registration event.
*/
private[camel] trait ConsumerEvent {
val uuid: String
}
/**
* A consumer actor (un)registration event.
*/
private[camel] trait ConsumerActorEvent extends ConsumerEvent {
val actorRef: ActorRef
val actor: Consumer
val uuid = actorRef.uuid.toString
val endpointUri = actor.endpointUri
val blocking = actor.blocking
val autoack = actor.autoack
val routeDefinitionHandler = actor.routeDefinitionHandler
}
/**
* Event indicating that a consumer actor has been registered at the actor registry.
*/
private[camel] case class ConsumerActorRegistered(actorRef: ActorRef, actor: Consumer) extends ConsumerActorEvent
/**
* Event indicating that a consumer actor has been unregistered from the actor registry.
*/
private[camel] case class ConsumerActorUnregistered(actorRef: ActorRef, actor: Consumer) extends ConsumerActorEvent
/**
* @author Martin Krasser
*/
private[camel] object ConsumerActorRegistered {
/**
* Creates an ConsumerActorRegistered event message for a consumer actor or None if
* <code>actorRef</code> is not a consumer actor.
*/
def eventFor(actorRef: ActorRef): Option[ConsumerActorRegistered] = {
Consumer.withConsumer[ConsumerActorRegistered](actorRef) { actor
ConsumerActorRegistered(actorRef, actor)
}
}
}
/**
* @author Martin Krasser
*/
private[camel] object ConsumerActorUnregistered {
/**
* Creates an ConsumerActorUnregistered event message for a consumer actor or None if
* <code>actorRef</code> is not a consumer actor.
*/
def eventFor(actorRef: ActorRef): Option[ConsumerActorUnregistered] = {
Consumer.withConsumer[ConsumerActorUnregistered](actorRef) { actor
ConsumerActorUnregistered(actorRef, actor)
}
}
}

View file

@ -1,385 +0,0 @@
/**
* Copyright (C) 2009-2010 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camel
import java.util.{ Map JMap, Set JSet }
import scala.collection.JavaConversions._
import org.apache.camel.{ Exchange, Message CamelMessage }
import org.apache.camel.util.ExchangeHelper
import akka.japi.{ Function JFunction }
/**
* An immutable representation of a Camel message.
*
* @author Martin Krasser
*/
case class Message(val body: Any, val headers: Map[String, Any] = Map.empty) {
/**
* Creates a Message with given body and empty headers map.
*/
def this(body: Any) = this(body, Map.empty[String, Any])
/**
* Creates a Message with given body and headers map. A copy of the headers map is made.
* <p>
* Java API
*/
def this(body: Any, headers: JMap[String, Any]) = this(body, headers.toMap)
/**
* Returns the body of the message converted to the type <code>T</code>. Conversion is done
* using Camel's type converter. The type converter is obtained from the CamelContext managed
* by CamelContextManager. Applications have to ensure proper initialization of
* CamelContextManager.
*
* @see CamelContextManager.
*/
def bodyAs[T](implicit m: Manifest[T]): T = getBodyAs(m.erasure.asInstanceOf[Class[T]])
/**
* Returns the body of the message converted to the type as given by the <code>clazz</code>
* parameter. Conversion is done using Camel's type converter. The type converter is obtained
* from the CamelContext managed by CamelContextManager. Applications have to ensure proper
* initialization of CamelContextManager.
* <p>
* Java API
*
* @see CamelContextManager.
*/
def getBodyAs[T](clazz: Class[T]): T =
CamelContextManager.mandatoryContext.getTypeConverter.mandatoryConvertTo[T](clazz, body)
/**
* Returns those headers from this message whose name is contained in <code>names</code>.
*/
def headers(names: Set[String]): Map[String, Any] = headers.filter(names contains _._1)
/**
* Returns those headers from this message whose name is contained in <code>names</code>.
* The returned headers map is backed up by an immutable headers map. Any attempt to modify
* the returned map will throw an exception.
* <p>
* Java API
*/
def getHeaders(names: JSet[String]): JMap[String, Any] = headers.filter(names contains _._1)
/**
* Returns all headers from this message. The returned headers map is backed up by this
* message's immutable headers map. Any attempt to modify the returned map will throw an
* exception.
* <p>
* Java API
*/
def getHeaders: JMap[String, Any] = headers
/**
* 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>. Throws <code>NoSuchElementException</code>
* if the header doesn't exist.
* <p>
* Java API
*/
def getHeader(name: String): Any = header(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 =
getHeaderAs(name, m.erasure.asInstanceOf[Class[T]])
/**
* Returns the header with given <code>name</code> converted to type as given by the <code>clazz</code>
* parameter. Throws <code>NoSuchElementException</code> if the header doesn't exist.
* <p>
* Java API
*/
def getHeaderAs[T](name: String, clazz: Class[T]): T =
CamelContextManager.mandatoryContext.getTypeConverter.mandatoryConvertTo[T](clazz, header(name))
/**
* Creates a Message with a transformed body using a <code>transformer</code> function.
*/
def transformBody[A](transformer: A Any): Message = setBody(transformer(body.asInstanceOf[A]))
/**
* Creates a Message with a transformed body using a <code>transformer</code> function.
* <p>
* Java API
*/
def transformBody[A](transformer: JFunction[A, Any]): Message = setBody(transformer(body.asInstanceOf[A]))
/**
* Creates a Message with current <code>body</code> converted to type <code>T</code>.
*/
def setBodyAs[T](implicit m: Manifest[T]): Message = setBodyAs(m.erasure.asInstanceOf[Class[T]])
/**
* Creates a Message with current <code>body</code> converted to type <code>clazz</code>.
* <p>
* Java API
*/
def setBodyAs[T](clazz: Class[T]): Message = setBody(getBodyAs(clazz))
/**
* Creates a Message with a given <code>body</code>.
*/
def setBody(body: Any) = new Message(body, this.headers)
/**
* Creates a new Message with given <code>headers</code>.
*/
def setHeaders(headers: Map[String, Any]): Message = copy(this.body, headers)
/**
* Creates a new Message with given <code>headers</code>. A copy of the headers map is made.
* <p>
* Java API
*/
def setHeaders(headers: JMap[String, Any]): Message = setHeaders(headers.toMap)
/**
* Creates a new Message with given <code>headers</code> added to the current headers.
*/
def addHeaders(headers: Map[String, Any]): Message = copy(this.body, this.headers ++ headers)
/**
* Creates a new Message with given <code>headers</code> added to the current headers.
* A copy of the headers map is made.
* <p>
* Java API
*/
def addHeaders(headers: JMap[String, Any]): Message = addHeaders(headers.toMap)
/**
* Creates a new Message with the given <code>header</code> added to the current headers.
*/
def addHeader(header: (String, Any)): Message = copy(this.body, this.headers + header)
/**
* Creates a new Message with the given header, represented by <code>name</code> and
* <code>value</code> added to the existing headers.
* <p>
* Java API
*/
def addHeader(name: String, value: Any): Message = addHeader((name, value))
/**
* Creates a new Message where the header with given <code>headerName</code> is removed from
* the existing headers.
*/
def removeHeader(headerName: String) = copy(this.body, this.headers - headerName)
}
/**
* Companion object of Message class.
*
* @author Martin Krasser
*/
object Message {
/**
* Message header to correlate request with response messages. Applications that send
* messages to a Producer actor may want to set this header on the request message
* so that it can be correlated with an asynchronous response. Messages send to Consumer
* actors have this header already set.
*/
val MessageExchangeId = "MessageExchangeId".intern
/**
* Creates a canonical form of the given message <code>msg</code>. If <code>msg</code> of type
* Message then <code>msg</code> is returned, otherwise <code>msg</code> is set as body of a
* newly created Message object.
*/
def canonicalize(msg: Any) = msg match {
case mobj: Message mobj
case body new Message(body)
}
}
/**
* Positive acknowledgement message (used for system-acknowledged message receipts).
*
* @author Martin Krasser
*/
case object Ack {
/** Java API to get the Ack singleton */
def ack = this
}
/**
* An immutable representation of a failed Camel exchange. It contains the failure cause
* obtained from Exchange.getException and the headers from either the Exchange.getIn
* message or Exchange.getOut message, depending on the exchange pattern.
*
* @author Martin Krasser
*/
case class Failure(val cause: Throwable, val headers: Map[String, Any] = Map.empty) {
/**
* Creates a Failure with cause body and empty headers map.
*/
def this(cause: Throwable) = this(cause, Map.empty[String, Any])
/**
* Creates a Failure with given cause and headers map. A copy of the headers map is made.
* <p>
* Java API
*/
def this(cause: Throwable, headers: JMap[String, Any]) = this(cause, headers.toMap)
/**
* Returns the cause of this Failure.
* <p>
* Java API.
*/
def getCause = cause
/**
* Returns all headers from this failure message. The returned headers map is backed up by
* this message's immutable headers map. Any attempt to modify the returned map will throw
* an exception.
* <p>
* Java API
*/
def getHeaders: JMap[String, Any] = headers
}
/**
* Adapter for converting an org.apache.camel.Exchange to and from Message and Failure objects.
*
* @author Martin Krasser
*/
class CamelExchangeAdapter(exchange: Exchange) {
import CamelMessageConversion.toMessageAdapter
/**
* Sets Exchange.getIn from the given Message object.
*/
def fromRequestMessage(msg: Message): Exchange = { requestMessage.fromMessage(msg); exchange }
/**
* Depending on the exchange pattern, sets Exchange.getIn or Exchange.getOut from the given
* Message object. If the exchange is out-capable then the Exchange.getOut is set, otherwise
* Exchange.getIn.
*/
def fromResponseMessage(msg: Message): Exchange = { responseMessage.fromMessage(msg); exchange }
/**
* Sets Exchange.getException from the given Failure message. Headers of the Failure message
* are ignored.
*/
def fromFailureMessage(msg: Failure): Exchange = { exchange.setException(msg.cause); exchange }
/**
* Creates a Message object from Exchange.getIn.
*/
def toRequestMessage: Message = toRequestMessage(Map.empty)
/**
* Depending on the exchange pattern, creates a Message object from Exchange.getIn or Exchange.getOut.
* If the exchange is out-capable then the Exchange.getOut is set, otherwise Exchange.getIn.
*/
def toResponseMessage: Message = toResponseMessage(Map.empty)
/**
* Creates a Failure object from the adapted Exchange.
*
* @see Failure
*/
def toFailureMessage: Failure = toFailureMessage(Map.empty)
/**
* Creates a Message object from Exchange.getIn.
*
* @param headers additional headers to set on the created Message in addition to those
* in the Camel message.
*/
def toRequestMessage(headers: Map[String, Any]): Message = requestMessage.toMessage(headers)
/**
* Depending on the exchange pattern, creates a Message object from Exchange.getIn or Exchange.getOut.
* If the exchange is out-capable then the Exchange.getOut is set, otherwise Exchange.getIn.
*
* @param headers additional headers to set on the created Message in addition to those
* in the Camel message.
*/
def toResponseMessage(headers: Map[String, Any]): Message = responseMessage.toMessage(headers)
/**
* Creates a Failure object from the adapted Exchange.
*
* @param headers additional headers to set on the created Message in addition to those
* in the Camel message.
*
* @see Failure
*/
def toFailureMessage(headers: Map[String, Any]): Failure =
Failure(exchange.getException, headers ++ responseMessage.toMessage.headers)
private def requestMessage = exchange.getIn
private def responseMessage = ExchangeHelper.getResultMessage(exchange)
}
/**
* Adapter for converting an org.apache.camel.Message to and from Message objects.
*
* @author Martin Krasser
*/
class CamelMessageAdapter(val cm: CamelMessage) {
/**
* Set the adapted Camel message from the given Message object.
*/
def fromMessage(m: Message): CamelMessage = {
cm.setBody(m.body)
for (h m.headers) cm.getHeaders.put(h._1, h._2.asInstanceOf[AnyRef])
cm
}
/**
* Creates a new Message object from the adapted Camel message.
*/
def toMessage: Message = toMessage(Map.empty)
/**
* Creates a new Message object from the adapted Camel message.
*
* @param headers additional headers to set on the created Message in addition to those
* in the Camel message.
*/
def toMessage(headers: Map[String, Any]): Message = Message(cm.getBody, cmHeaders(headers, cm))
private def cmHeaders(headers: Map[String, Any], cm: CamelMessage) = headers ++ cm.getHeaders
}
/**
* Defines conversion methods to CamelExchangeAdapter and CamelMessageAdapter.
* Imported by applications that implicitly want to use conversion methods of
* CamelExchangeAdapter and CamelMessageAdapter.
*/
object CamelMessageConversion {
/**
* Creates an CamelExchangeAdapter for the given Camel exchange.
*/
implicit def toExchangeAdapter(ce: Exchange): CamelExchangeAdapter =
new CamelExchangeAdapter(ce)
/**
* Creates an CamelMessageAdapter for the given Camel message.
*/
implicit def toMessageAdapter(cm: CamelMessage): CamelMessageAdapter =
new CamelMessageAdapter(cm)
}

View file

@ -1,16 +1,12 @@
/**
* Copyright (C) 2009-2010 Typesafe Inc. <http://www.typesafe.com>
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camel
import CamelMessageConversion.toExchangeAdapter
import org.apache.camel._
import org.apache.camel.processor.SendProcessor
import akka.actor.{ Actor, ActorRef, UntypedActor }
import akka.dispatch.ActorPromise
import akka.actor.Actor
import internal.CamelExchangeAdapter
import org.apache.camel.{ Exchange, ExchangePattern, AsyncCallback }
/**
* Support trait for producing messages to Camel endpoints.
@ -18,22 +14,14 @@ import akka.dispatch.ActorPromise
* @author Martin Krasser
*/
trait ProducerSupport { this: Actor
protected[this] implicit def camel = CamelExtension(context.system)
protected[this] lazy val (endpoint, processor) = camel.registerProducer(self, endpointUri)
/**
* Message headers to copy by default from request message to response-message.
* CamelMessage headers to copy by default from request message to response-message.
*/
private val headersToCopyDefault = Set(Message.MessageExchangeId)
/**
* <code>Endpoint</code> object resolved from the current CamelContext with
* <code>endpointUri</code>.
*/
private lazy val endpoint = CamelContextManager.mandatoryContext.getEndpoint(endpointUri)
/**
* <code>SendProcessor</code> for producing messages to <code>endpoint</code>.
*/
private lazy val processor = createSendProcessor
private val headersToCopyDefault = Set(CamelMessage.MessageExchangeId)
/**
* If set to false (default), this producer expects a response message from the Camel endpoint.
@ -49,33 +37,11 @@ trait ProducerSupport { this: Actor ⇒
/**
* Returns the names of message headers to copy from a request message to a response message.
* By default only the Message.MessageExchangeId is copied. Applications may override this to
* define an system-specific set of message headers to copy.
* By default only the CamelMessage.MessageExchangeId is copied. Applications may override this to
* define an application-specific set of message headers to copy.
*/
def headersToCopy: Set[String] = headersToCopyDefault
/**
* Default implementation of <code>Actor.preRestart</code> for freeing resources needed
* to actually send messages to <code>endpointUri</code>.
*/
override def preRestart(reason: Throwable, msg: Option[Any]) {
try { preRestartProducer(reason) } finally { processor.stop }
}
/**
* Does nothing by default. Can be overridden by concrete producers for implementing a
* pre-restart callback handler.
*/
def preRestartProducer(reason: Throwable) {}
/**
* Default implementation of <code>Actor.postStop</code> for freeing resources needed
* to actually send messages to <code>endpointUri</code>.
*/
override def postStop {
processor.stop
}
/**
* Initiates a message exchange of given <code>pattern</code> with the endpoint specified by
* <code>endpointUri</code>. The in-message of the initiated exchange is the canonical form
@ -83,44 +49,29 @@ trait ProducerSupport { this: Actor ⇒
* as argument to <code>receiveAfterProduce</code>. If the response is received synchronously from
* the endpoint then <code>receiveAfterProduce</code> is called synchronously as well. If the
* response is received asynchronously, the <code>receiveAfterProduce</code> is called
* asynchronously. This is done by wrapping the response, adding it to this producers
* mailbox, unwrapping it and calling <code>receiveAfterProduce</code>. The original
* sender and senderFuture are thereby preserved.
* asynchronously. The original
* sender and senderFuture are preserved.
*
* @see Message#canonicalize(Any)
* @see CamelMessage#canonicalize(Any)
*
* @param msg message to produce
* @param pattern exchange pattern
*/
protected def produce(msg: Any, pattern: ExchangePattern) {
val cmsg = Message.canonicalize(msg)
val exchange = createExchange(pattern).fromRequestMessage(cmsg)
protected def produce(msg: Any, pattern: ExchangePattern): Unit = {
implicit def toExchangeAdapter(exchange: Exchange): CamelExchangeAdapter = new CamelExchangeAdapter(exchange)
val cmsg = CamelMessage.canonicalize(msg)
val exchange = endpoint.createExchange(pattern)
exchange.setRequest(cmsg)
processor.process(exchange, new AsyncCallback {
val producer = self
// Need copies of sender reference here since the callback could be done
// later by another thread.
val replyChannel = sender
def done(doneSync: Boolean) {
(doneSync, exchange.isFailed) match {
case (true, true) dispatchSync(exchange.toFailureMessage(cmsg.headers(headersToCopy)))
case (true, false) dispatchSync(exchange.toResponseMessage(cmsg.headers(headersToCopy)))
case (false, true) dispatchAsync(FailureResult(exchange.toFailureMessage(cmsg.headers(headersToCopy))))
case (false, false) dispatchAsync(MessageResult(exchange.toResponseMessage(cmsg.headers(headersToCopy))))
}
}
private def dispatchSync(result: Any) =
receiveAfterProduce(result)
private def dispatchAsync(result: Any) = {
replyChannel match {
case _: ActorPromise
producer.postMessageToMailboxAndCreateFutureResultWithTimeout(result, producer.timeout, replyChannel)
case _
producer.postMessageToMailbox(result, replyChannel)
}
}
val originalSender = sender
// Ignoring doneSync, sending back async uniformly.
def done(doneSync: Boolean): Unit = producer.tell(
if (exchange.isFailed) FailureResult(exchange.toFailureMessage(cmsg.headers(headersToCopy)))
else MessageResult(exchange.toResponseMessage(cmsg.headers(headersToCopy))), originalSender)
})
}
@ -162,20 +113,6 @@ trait ProducerSupport { this: Actor ⇒
case msg if (!oneway) sender ! msg
}
/**
* Creates a new Exchange of given <code>pattern</code> from the endpoint specified by
* <code>endpointUri</code>.
*/
private def createExchange(pattern: ExchangePattern): Exchange = endpoint.createExchange(pattern)
/**
* Creates a new <code>SendProcessor</code> for <code>endpoint</code>.
*/
private def createSendProcessor = {
val sendProcessor = new SendProcessor(endpoint)
sendProcessor.start()
sendProcessor
}
}
/**
@ -191,66 +128,14 @@ trait Producer extends ProducerSupport { this: Actor ⇒
}
/**
* 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 ProducerSupport {
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 a response was received from the endpoint specified by <code>endpointUri</code>. The
* response is passed as argument. By default, this method sends the response back to the original sender
* if <code>oneway</code> is <code>false</code>. If <code>oneway</code> is <code>true</code>, nothing is
* done. This method may be overridden by subclasses (e.g. to forward responses to another actor).
*/
@throws(classOf[Exception])
def onReceiveAfterProduce(message: Any): Unit = super.receiveAfterProduce(message)
}
private case class MessageResult(message: CamelMessage)
/**
* @author Martin Krasser
*/
private[camel] case class MessageResult(message: Message)
/**
* @author Martin Krasser
*/
private[camel] case class FailureResult(failure: Failure)
private case class FailureResult(failure: Failure)
/**
* A one-way producer.

View file

@ -1,64 +0,0 @@
package akka.camel
import akka.actor._
/**
* Base class for concrete (un)publish requestors. Subclasses are responsible for requesting
* (un)publication of consumer actors by calling <code>deliverCurrentEvent</code>.
*
* @author Martin Krasser
*/
private[camel] abstract class PublishRequestor extends Actor {
private val events = collection.mutable.Set[ConsumerEvent]()
private var publisher: Option[ActorRef] = None
def receiveActorRegistryEvent: Receive
/**
* Accepts
* <ul>
* <li><code>InitPublishRequestor</code> messages to configure a publisher for this requestor.</li>
* <li><code>ActorRegistryEvent</code> messages to be handled <code>receiveActorRegistryEvent</code>
* implementators</li>.
* </ul>
* Other messages are simply ignored. Calls to <code>deliverCurrentEvent</code> prior to setting a
* publisher are buffered. They will be sent after a publisher has been set.
*/
def receive = {
case InitPublishRequestor(pub) {
publisher = Some(pub)
deliverBufferedEvents
}
case e: ActorRegistryEvent receiveActorRegistryEvent(e)
case _ { /* ignore */ }
}
/**
* Deliver the given <code>event</code> to <code>publisher</code> or buffer the event if
* <code>publisher</code> is not defined yet.
*/
protected def deliverCurrentEvent(event: ConsumerEvent) {
publisher match {
case Some(pub) pub ! event
case None events += event
}
}
private def deliverBufferedEvents {
for (event events) deliverCurrentEvent(event)
events.clear
}
}
/**
* @author Martin Krasser
*/
private[camel] object PublishRequestor {
def pastActorRegisteredEvents = for (actor Actor.registry.local.actors) yield ActorRegistered(actor.address, actor)
}
/**
* Command message to initialize a PublishRequestor to use <code>publisher</code>
* for publishing consumer actors.
*/
private[camel] case class InitPublishRequestor(publisher: ActorRef)

View file

@ -1,36 +0,0 @@
/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camel
import org.apache.camel.CamelContext
import akka.event.EventHandler
import akka.util.ReflectiveAccess.getObjectFor
/**
* Module for reflective access to akka-camel-typed.
*
* @author Martin Krasser
*/
private[camel] object TypedCamelAccess {
val loader = getClass.getClassLoader
object TypedCamelModule {
type TypedCamelObject = {
def onCamelContextInit(context: CamelContext): Unit
def onCamelServiceStart(service: CamelService): Unit
def onCamelServiceStop(service: CamelService): Unit
}
val typedCamelObject: Option[TypedCamelObject] =
getObjectFor("akka.camel.TypedCamel$", loader) match {
case Right(value) Some(value)
case Left(exception)
EventHandler.debug(this, exception.toString)
None
}
}
}

View file

@ -1,302 +0,0 @@
/**
* Copyright (C) 2009-2010 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camel.component
import java.net.InetSocketAddress
import java.util.{ Map JMap }
import java.util.concurrent.TimeoutException
import java.util.concurrent.atomic.AtomicReference
import org.apache.camel._
import org.apache.camel.impl.{ DefaultProducer, DefaultEndpoint, DefaultComponent }
import akka.actor._
import akka.camel.{ Ack, Failure, Message }
import akka.camel.CamelMessageConversion.toExchangeAdapter
import scala.reflect.BeanProperty
import akka.dispatch._
/**
* @author Martin Krasser
*/
object ActorComponent {
/**
* Name of the message header containing the actor id or uuid.
*/
val ActorIdentifier = "CamelActorIdentifier"
}
/**
* Camel component for sending messages to and receiving replies from (untyped) actors.
*
* @see akka.camel.component.ActorEndpoint
* @see akka.camel.component.ActorProducer
*
* @author Martin Krasser
*/
class ActorComponent extends DefaultComponent {
def createEndpoint(uri: String, remaining: String, parameters: JMap[String, Object]): ActorEndpoint = {
val (idType, idValue) = parsePath(remaining)
new ActorEndpoint(uri, this, idType, idValue)
}
private def parsePath(remaining: String): Tuple2[String, Option[String]] = remaining match {
case null | "" throw new IllegalArgumentException("invalid path: [%s] - should be <actorid> or id:<actorid> or uuid:<actoruuid>" format remaining)
case id if id startsWith "id:" ("id", parseIdentifier(id substring 3))
case uuid if uuid startsWith "uuid:" ("uuid", parseIdentifier(uuid substring 5))
case id ("id", parseIdentifier(id))
}
private def parseIdentifier(identifier: String): Option[String] =
if (identifier.length > 0) Some(identifier) else None
}
/**
* Camel endpoint for sending messages to and receiving replies from (untyped) actors. Actors
* are referenced using <code>actor</code> endpoint URIs of the following format:
* <code>actor:<actor-id></code>,
* <code>actor:id:[<actor-id>]</code> and
* <code>actor:uuid:[<actor-uuid>]</code>,
* where <code><actor-id></code> refers to <code>ActorRef.id</code> and <code><actor-uuid></code>
* refers to the String-representation od <code>ActorRef.uuid</code>. In URIs that contain
* <code>id:</code> or <code>uuid:</code>, an actor identifier (id or uuid) is optional. In this
* case, the in-message of an exchange produced to this endpoint must contain a message header
* with name <code>CamelActorIdentifier</code> and a value that is the target actor's identifier.
* If the URI contains an actor identifier, a message with a <code>CamelActorIdentifier</code>
* header overrides the identifier in the endpoint URI.
*
* @see akka.camel.component.ActorComponent
* @see akka.camel.component.ActorProducer
*
* @author Martin Krasser
*/
class ActorEndpoint(uri: String,
comp: ActorComponent,
val idType: String,
val idValue: Option[String]) extends DefaultEndpoint(uri, comp) {
/**
* Whether to block caller thread during two-way message exchanges with (untyped) actors. This is
* set via the <code>blocking=true|false</code> endpoint URI parameter. Default value is
* <code>false</code>.
*/
@BeanProperty
var blocking: Boolean = false
/**
* Whether to auto-acknowledge one-way message exchanges with (untyped) actors. This is
* set via the <code>blocking=true|false</code> endpoint URI parameter. Default value is
* <code>true</code>. When set to <code>true</code> consumer actors need to additionally
* call <code>Consumer.ack</code> within <code>Actor.receive</code>.
*/
@BeanProperty
var autoack: Boolean = true
/**
* @throws UnsupportedOperationException
*/
def createConsumer(processor: Processor): Consumer =
throw new UnsupportedOperationException("actor consumer not supported yet")
/**
* Creates a new ActorProducer instance initialized with this endpoint.
*/
def createProducer: ActorProducer = new ActorProducer(this)
/**
* Returns true.
*/
def isSingleton: Boolean = true
}
/**
* Sends the in-message of an exchange to an (untyped) actor, identified by an
* actor endpoint URI or by a <code>CamelActorIdentifier</code> message header.
* <ul>
* <li>If the exchange pattern is out-capable and <code>blocking</code> is set to
* <code>true</code> then the producer waits for a reply, using the !! operator.</li>
* <li>If the exchange pattern is out-capable and <code>blocking</code> is set to
* <code>false</code> then the producer sends the message using the ! operator, together
* with a callback handler. The callback handler is an <code>ActorRef</code> that can be
* used by the receiving actor to asynchronously reply to the route that is sending the
* message.</li>
* <li>If the exchange pattern is in-only then the producer sends the message using the
* ! operator.</li>
* </ul>
*
* @see akka.camel.component.ActorComponent
* @see akka.camel.component.ActorEndpoint
*
* @author Martin Krasser
*/
class ActorProducer(val ep: ActorEndpoint) extends DefaultProducer(ep) with AsyncProcessor {
import ActorProducer._
private lazy val uuid = uuidFrom(ep.idValue.getOrElse(throw new ActorIdentifierNotSetException))
def process(exchange: Exchange) =
if (exchange.getPattern.isOutCapable) sendSync(exchange) else sendAsync(exchange)
def process(exchange: Exchange, callback: AsyncCallback): Boolean = {
(exchange.getPattern.isOutCapable, ep.blocking, ep.autoack) match {
case (true, true, _) {
sendSync(exchange)
callback.done(true)
true
}
case (true, false, _) {
sendAsync(exchange, Some(AsyncCallbackAdapter(exchange, callback)))
false
}
case (false, false, true) {
sendAsync(exchange)
callback.done(true)
true
}
case (false, false, false) {
sendAsync(exchange, Some(AsyncCallbackAdapter(exchange, callback)))
false
}
case (false, true, false) {
sendSync(exchange)
callback.done(true)
true
}
case (false, true, true) {
throw new IllegalStateException("cannot have blocking=true and autoack=true for in-only message exchanges")
}
}
}
private def sendSync(exchange: Exchange) = {
val actor = target(exchange)
val result: Any = try { Some(Await.result((actor ? requestFor(exchange), 5 seconds)) } catch { case e Some(Failure(e)) }
result match {
case Some(Ack) { /* no response message to set */ }
case Some(msg: Failure) exchange.fromFailureMessage(msg)
case Some(msg) exchange.fromResponseMessage(Message.canonicalize(msg))
case None throw new TimeoutException("timeout (%d ms) while waiting response from %s"
format (actor.timeout, ep.getEndpointUri))
}
}
private def sendAsync(exchange: Exchange, sender: Option[ActorRef] = None) =
target(exchange).!(requestFor(exchange))(sender)
private def target(exchange: Exchange) =
targetOption(exchange) getOrElse (throw new ActorNotRegisteredException(ep.getEndpointUri))
private def targetOption(exchange: Exchange): Option[ActorRef] = ep.idType match {
case "id" targetById(targetId(exchange))
case "uuid" targetByUuid(targetUuid(exchange))
}
private def targetId(exchange: Exchange) = exchange.getIn.getHeader(ActorComponent.ActorIdentifier) match {
case id: String id
case null ep.idValue.getOrElse(throw new ActorIdentifierNotSetException)
}
private def targetUuid(exchange: Exchange) = exchange.getIn.getHeader(ActorComponent.ActorIdentifier) match {
case uuid: Uuid uuid
case uuid: String uuidFrom(uuid)
case null uuid
}
private def targetById(id: String) = Actor.registry.local.actorFor(id)
private def targetByUuid(uuid: Uuid) = Actor.registry.local.actorFor(uuid)
}
/**
* @author Martin Krasser
*/
private[camel] object ActorProducer {
def requestFor(exchange: Exchange) =
exchange.toRequestMessage(Map(Message.MessageExchangeId -> exchange.getExchangeId))
}
/**
* Thrown to indicate that an actor referenced by an endpoint URI cannot be
* found in the Actor.registry.
*
* @author Martin Krasser
*/
class ActorNotRegisteredException(uri: String) extends RuntimeException {
override def getMessage = "'%s' not registered" format uri
}
/**
* Thrown to indicate that no actor identifier has been set.
*
* @author Martin Krasser
*/
class ActorIdentifierNotSetException extends RuntimeException {
override def getMessage = "actor identifier not set"
}
/**
* @author Martin Krasser
*/
private[akka] object AsyncCallbackAdapter {
/**
* Creates and starts an <code>AsyncCallbackAdapter</code>.
*
* @param exchange message exchange to write results to.
* @param callback callback object to generate completion notifications.
*/
def apply(exchange: Exchange, callback: AsyncCallback) =
new AsyncCallbackAdapter(exchange, callback).start
}
/**
* Adapts an <code>ActorRef</code> to a Camel <code>AsyncCallback</code>. Used by receiving actors to reply
* asynchronously to Camel routes with <code>ActorRef.reply</code>.
* <p>
* <em>Please note</em> that this adapter can only be used locally at the moment which should not
* be a problem is most situations since Camel endpoints are only activated for local actor references,
* never for remote references.
*
* @author Martin Krasser
*/
private[akka] class AsyncCallbackAdapter(exchange: Exchange, callback: AsyncCallback) extends ActorRef with ScalaActorRef {
import akka.camel.Consumer._
val address = exchange.getExchangeId
@volatile
private var running: Boolean = true
def isTerminated: Boolean = !running
def suspend(): Unit = ()
def resume(): Unit = ()
def stop() { running = false }
/**
* Populates the initial <code>exchange</code> with the reply <code>message</code> and uses the
* <code>callback</code> handler to notify Camel about the asynchronous completion of the message
* exchange.
*
* @param message reply message
* @param sender ignored
*/
protected[akka] def postMessageToMailbox(message: Any, sender: ActorRef) = if(running) {
message match {
case Ack { /* no response message to set */ }
case msg: Failure exchange.fromFailureMessage(msg)
case msg exchange.fromResponseMessage(Message.canonicalize(msg))
}
callback.done(false)
}
def ?(message: Any)(implicit timeout: Timeout): Future[Any] =
Promise.failed(new UnsupportedOperationException("Ask/? is not supported for %s".format(getClass.getName)))
def restart(reason: Throwable): Unit = unsupported
private def unsupported = throw new UnsupportedOperationException("Not supported for %s" format classOf[AsyncCallbackAdapter].getName)
}

View file

@ -0,0 +1,117 @@
/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camel.internal
import akka.actor._
import collection.mutable.WeakHashMap
/**
* For internal use only. Tracks activation and de-activation of endpoints.
*/
private[akka] final class ActivationTracker extends Actor with ActorLogging {
val activations = new WeakHashMap[ActorRef, ActivationStateMachine]
/**
* A state machine that keeps track of the endpoint activation status of an actor.
*/
class ActivationStateMachine {
type State = PartialFunction[ActivationMessage, Unit]
var receive: State = notActivated()
/**
* Not activated state
* @return a partial function that handles messages in the 'not activated' state
*/
def notActivated(): State = {
var awaitingActivation = List[ActorRef]()
var awaitingDeActivation = List[ActorRef]()
{
case AwaitActivation(ref) awaitingActivation ::= sender
case AwaitDeActivation(ref) awaitingDeActivation ::= sender
case msg @ EndpointActivated(ref)
awaitingActivation.foreach(_ ! msg)
receive = activated(awaitingDeActivation)
case EndpointFailedToActivate(ref, cause)
awaitingActivation.foreach(_ ! EndpointFailedToActivate(ref, cause))
receive = failedToActivate(cause)
}
}
/**
* Activated state.
* @param currentAwaitingDeActivation the current <code>ActorRef</code>s awaiting de-activation
* @return a partial function that handles messages in the 'activated' state
*/
def activated(currentAwaitingDeActivation: List[ActorRef]): State = {
var awaitingDeActivation = currentAwaitingDeActivation
{
case AwaitActivation(ref) sender ! EndpointActivated(ref)
case AwaitDeActivation(ref) awaitingDeActivation ::= sender
case msg @ EndpointDeActivated(ref)
awaitingDeActivation foreach (_ ! msg)
receive = deactivated
case msg @ EndpointFailedToDeActivate(ref, cause)
awaitingDeActivation foreach (_ ! msg)
receive = failedToDeActivate(cause)
}
}
/**
* De-activated state
* @return a partial function that handles messages in the 'de-activated' state
*/
def deactivated: State = {
case AwaitActivation(ref) sender ! EndpointActivated(ref)
case AwaitDeActivation(ref) sender ! EndpointDeActivated(ref)
}
/**
* Failed to activate state
* @param cause the cause for the failure
* @return a partial function that handles messages in 'failed to activate' state
*/
def failedToActivate(cause: Throwable): State = {
case AwaitActivation(ref) sender ! EndpointFailedToActivate(ref, cause)
case AwaitDeActivation(ref) sender ! EndpointFailedToActivate(ref, cause)
}
/**
* Failed to de-activate state
* @param cause the cause for the failure
* @return a partial function that handles messages in 'failed to de-activate' state
*/
def failedToDeActivate(cause: Throwable): State = {
case AwaitActivation(ref) sender ! EndpointActivated(ref)
case AwaitDeActivation(ref) sender ! EndpointFailedToDeActivate(ref, cause)
}
}
/**
* Subscribes self to messages of type <code>ActivationMessage</code>
*/
override def preStart() {
context.system.eventStream.subscribe(self, classOf[ActivationMessage])
}
/**
*
* @return
*/
override def receive = {
case msg @ ActivationMessage(ref)
val state = activations.getOrElseUpdate(ref, new ActivationStateMachine)
(state.receive orElse logStateWarning(ref))(msg)
}
private[this] def logStateWarning(actorRef: ActorRef): Receive = { case msg log.warning("Message [{}] not expected in current state of actor [{}]", msg, actorRef) }
}
case class AwaitActivation(ref: ActorRef) extends ActivationMessage(ref)
case class AwaitDeActivation(ref: ActorRef) extends ActivationMessage(ref)

View file

@ -0,0 +1,92 @@
package akka.camel.internal
import java.util.{ Map JMap, Set JSet }
import scala.collection.JavaConversions._
import org.apache.camel.util.ExchangeHelper
import akka.japi.{ Function JFunction }
import org.apache.camel.{ Exchange, Message JCamelMessage }
import akka.camel.{ Failure, CamelMessage }
/**
* For internal use only.
* Adapter for converting an org.apache.camel.Exchange to and from CamelMessage and Failure objects.
*
* @author Martin Krasser
*/
private[camel] class CamelExchangeAdapter(exchange: Exchange) {
def getExchangeId = exchange.getExchangeId
def isOutCapable = exchange.getPattern.isOutCapable
/**
* Sets Exchange.getIn from the given CamelMessage object.
*/
def setRequest(msg: CamelMessage) { msg.copyContentTo(request) }
/**
* Depending on the exchange pattern, sets Exchange.getIn or Exchange.getOut from the given
* CamelMessage object. If the exchange is out-capable then the Exchange.getOut is set, otherwise
* Exchange.getIn.
*/
def setResponse(msg: CamelMessage) { msg.copyContentTo(response) }
/**
* Sets Exchange.getException from the given Failure message. Headers of the Failure message
* are ignored.
*/
def setFailure(msg: Failure) { exchange.setException(msg.cause) }
/**
* Creates a CamelMessage object from Exchange.getIn.
*/
def toRequestMessage: CamelMessage = toRequestMessage(Map.empty)
/**
* Depending on the exchange pattern, creates a CamelMessage object from Exchange.getIn or Exchange.getOut.
* If the exchange is out-capable then the Exchange.getOut is set, otherwise Exchange.getIn.
*/
def toResponseMessage: CamelMessage = toResponseMessage(Map.empty)
/**
* Creates a Failure object from the adapted Exchange.
*
* @see Failure
*/
def toFailureMessage: Failure = toFailureMessage(Map.empty)
/**
* Creates a CamelMessage object from Exchange.getIn.
*
* @param headers additional headers to set on the created CamelMessage in addition to those
* in the Camel message.
*/
def toRequestMessage(headers: Map[String, Any]): CamelMessage = CamelMessage.from(request, headers)
/**
* Depending on the exchange pattern, creates a CamelMessage object from Exchange.getIn or Exchange.getOut.
* If the exchange is out-capable then the Exchange.getOut is set, otherwise Exchange.getIn.
*
* @param headers additional headers to set on the created CamelMessage in addition to those
* in the Camel message.
*/
def toResponseMessage(headers: Map[String, Any]): CamelMessage = CamelMessage.from(response, headers)
/**
* Creates a Failure object from the adapted Exchange.
*
* @param headers additional headers to set on the created CamelMessage in addition to those
* in the Camel message.
*
* @see Failure
*/
def toFailureMessage(headers: Map[String, Any]): Failure =
Failure(exchange.getException, headers ++ response.getHeaders)
private def request = exchange.getIn
private def response: JCamelMessage = ExchangeHelper.getResultMessage(exchange)
}

View file

@ -0,0 +1,190 @@
/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camel.internal
import akka.camel._
import component.ActorEndpointPath
import java.io.InputStream
import org.apache.camel.builder.RouteBuilder
import akka.actor._
import collection.mutable
import org.apache.camel.model.RouteDefinition
import org.apache.camel.CamelContext
import akka.util.Duration
/**
* For internal use only.
* Manages consumer registration. Consumers call registerConsumer method to register themselves when they get created.
* ActorEndpoint uses it to lookup an actor by its path.
*/
private[camel] trait ConsumerRegistry { this: Activation
def system: ActorSystem
def context: CamelContext
/**
* For internal use only.
*/
private[this] lazy val idempotentRegistry = system.actorOf(Props(new IdempotentCamelConsumerRegistry(context)))
/**
* For internal use only.
* @param endpointUri the URI to register the consumer on
* @param consumer the consumer
* @param activationTimeout the timeout for activation
* @return the actorRef to the consumer
*/
private[camel] def registerConsumer(endpointUri: String, consumer: Consumer, activationTimeout: Duration) = {
idempotentRegistry ! RegisterConsumer(endpointUri, consumer.self, consumer)
awaitActivation(consumer.self, activationTimeout)
}
}
/**
* For internal use only.
* Guarantees idempotent registration of camel consumer endpoints.
*
* Once registered the consumer is watched and unregistered upon termination.
* It also publishes events to the eventStream so interested parties could subscribe to them.
* The main consumer of these events is currently the ActivationTracker.
*/
private[camel] class IdempotentCamelConsumerRegistry(camelContext: CamelContext) extends Actor {
case class UnregisterConsumer(actorRef: ActorRef)
val activated = new mutable.HashSet[ActorRef]
val registrator = context.actorOf(Props(new CamelConsumerRegistrator))
def receive = {
case msg @ RegisterConsumer(_, consumer, _)
if (!isAlreadyActivated(consumer)) {
activated.add(consumer)
registrator ! msg
}
case msg @ EndpointActivated(consumer)
context.watch(consumer)
context.system.eventStream.publish(msg)
case msg @ EndpointFailedToActivate(consumer, _)
activated.remove(consumer)
context.system.eventStream.publish(msg)
case Terminated(ref)
activated.remove(ref)
registrator ! UnregisterConsumer(ref)
case msg @ EndpointFailedToDeActivate(ref, cause) context.system.eventStream.publish(msg)
case msg: EndpointDeActivated context.system.eventStream.publish(msg)
}
def isAlreadyActivated(ref: ActorRef): Boolean = activated.contains(ref)
class CamelConsumerRegistrator extends Actor with ActorLogging {
def receive = {
case RegisterConsumer(endpointUri, consumer, consumerConfig)
camelContext.addRoutes(new ConsumerActorRouteBuilder(endpointUri, consumer, consumerConfig))
context.sender ! EndpointActivated(consumer)
log.debug("Published actor [{}] at endpoint [{}]", consumerConfig, endpointUri)
case UnregisterConsumer(consumer)
camelContext.stopRoute(consumer.path.toString)
context.sender ! EndpointDeActivated(consumer)
log.debug("Unpublished actor [{}] from endpoint [{}]", consumer, consumer.path)
}
override def preRestart(reason: Throwable, message: Option[Any]) {
//FIXME check logic
super.preStart()
message match {
case Some(RegisterConsumer(_, consumer, _)) sender ! EndpointFailedToActivate(consumer, reason)
case Some(UnregisterConsumer(consumer)) sender ! EndpointFailedToDeActivate(consumer, reason)
case _
}
}
}
}
/**
* For internal use only. A message to register a consumer.
* @param endpointUri the endpointUri to register to
* @param actorRef the actorRef to register as a consumer
* @param config the configuration for the consumer
*/
private[camel] case class RegisterConsumer(endpointUri: String, actorRef: ActorRef, config: ConsumerConfig)
/**
* For internal use only.
* Abstract builder of a route to a target which can be either an actor or an typed actor method.
*
* @param endpointUri endpoint URI of the consumer actor or typed actor method.
*
* @author Martin Krasser
*/
private[camel] class ConsumerActorRouteBuilder(endpointUri: String, consumer: ActorRef, config: ConsumerConfig) extends RouteBuilder {
//TODO: what if actorpath contains parameters? Should we use url encoding? But this will look ugly...
protected def targetActorUri = ActorEndpointPath(consumer).toCamelPath(config)
def configure() {
val scheme = endpointUri take endpointUri.indexOf(":") // e.g. "http" from "http://whatever/..."
val route = from(endpointUri).routeId(consumer.path.toString)
val converted = Conversions(scheme, route)
val userCustomized = applyUserRouteCustomization(converted)
userCustomized.to(targetActorUri)
}
def applyUserRouteCustomization(rd: RouteDefinition) = config.onRouteDefinition(rd)
object Conversions {
// TODO: make conversions configurable
private val bodyConversions = Map(
"file" -> classOf[InputStream])
def apply(scheme: String, routeDefinition: RouteDefinition): RouteDefinition = bodyConversions.get(scheme) match {
case Some(clazz) routeDefinition.convertBodyTo(clazz)
case None routeDefinition
}
}
}
/**
* Super class of all activation messages.
*/
abstract class ActivationMessage(val actor: ActorRef)
/**
* For internal use only. companion object of <code>ActivationMessage</code>
*
*/
private[camel] object ActivationMessage {
def unapply(msg: ActivationMessage): Option[ActorRef] = Some(msg.actor)
}
/**
* For internal use only.
* Event message indicating that a single endpoint has been activated.
*/
sealed case class EndpointActivated(actorRef: ActorRef) extends ActivationMessage(actorRef)
/**
* For internal use only.
* Event message indicating that a single endpoint failed tp activate
* @param actorRef the endpoint that failed to activate
* @param cause the cause for failure
*/
sealed case class EndpointFailedToActivate(actorRef: ActorRef, cause: Throwable) extends ActivationMessage(actorRef)
/**
* For internal use only.
* @param actorRef the endpoint that was de-activated
*/
sealed case class EndpointDeActivated(actorRef: ActorRef) extends ActivationMessage(actorRef)
/**
* For internal use only.
* @param actorRef the endpoint that failed to de-activate
* @param cause the cause for failure
*/
sealed case class EndpointFailedToDeActivate(actorRef: ActorRef, cause: Throwable) extends ActivationMessage(actorRef)

View file

@ -0,0 +1,63 @@
package akka.camel.internal
import akka.actor.ActorSystem
import component.{ DurationTypeConverter, ActorComponent }
import org.apache.camel.CamelContext
import org.apache.camel.impl.DefaultCamelContext
import scala.Predef._
import akka.event.Logging
import akka.camel.Camel
import akka.util.{ NonFatal, Duration }
/**
* For internal use only.
* Creates an instance of the Camel subsystem.
*
* @param system is used to create internal actors needed by camel instance.
* Camel doesn't maintain the lifecycle of this actor system. The actor system has to be shut down by the user.
* In the typical scenario, when camel is used with akka extension, it is natural that camel reuses the actor system it extends.
* Also by not creating extra internal actor system we are conserving resources.
*/
private[camel] class DefaultCamel(val system: ActorSystem) extends Camel {
/**
* For internal use only.
*/
private[camel] implicit val log = Logging(system, "Camel")
lazy val context: CamelContext = {
val ctx = new DefaultCamelContext
ctx.setName(system.name);
ctx.setStreamCaching(true)
ctx.addComponent("actor", new ActorComponent(this))
ctx.getTypeConverterRegistry.addTypeConverter(classOf[Duration], classOf[String], DurationTypeConverter)
ctx
}
lazy val template = context.createProducerTemplate()
/**
* Starts camel and underlying camel context and template.
* Only the creator of Camel should start and stop it.
* @see akka.camel.DefaultCamel#stop()
*/
def start = {
context.start()
try template.start() catch { case NonFatal(e) context.stop(); throw e }
log.debug("Started CamelContext[{}] for ActorSystem[{}]", context.getName, system.name)
this
}
/**
* Stops camel and underlying camel context and template.
* Only the creator of Camel should shut it down.
* There is no need to stop Camel instance, which you get from the CamelExtension, as its lifecycle is bound to the actor system.
*
* @see akka.camel.DefaultCamel#start()
*/
def shutdown() {
try context.stop() finally {
try { template.stop() } catch { case NonFatal(e) log.debug("Swallowing non-fatal exception [{}] on stopping Camel producer template", e) }
}
log.debug("Stopped CamelContext[{}] for ActorSystem[{}]", context.getName, system.name)
}
}

View file

@ -0,0 +1,84 @@
package akka.camel.internal
import java.util.concurrent.ConcurrentHashMap
import org.apache.camel.processor.SendProcessor
import akka.actor.{ Props, ActorRef, Terminated, Actor }
import org.apache.camel.Endpoint
import akka.camel.Camel
import akka.util.NonFatal
/**
* Watches the end of life of <code>Producer</code>s.
* Removes a <code>Producer</code> from the <code>ProducerRegistry</code> when it is <code>Terminated</code>,
* which in turn stops the <code>SendProcessor</code>.
*/
private class ProducerWatcher(registry: ProducerRegistry) extends Actor {
override def receive = {
case RegisterProducer(actorRef) context.watch(actorRef)
case Terminated(actorRef) registry.unregisterProducer(actorRef)
}
}
private case class RegisterProducer(actorRef: ActorRef)
/**
* For internal use only.
* Manages the Camel objects for <code>Producer</code>s.
* Every <code>Producer</code> needs an <code>Endpoint</code> and a <code>SendProcessor</code>
* to produce messages over an <code>Exchange</code>.
*/
private[camel] trait ProducerRegistry {
this: Camel
private val camelObjects = new ConcurrentHashMap[ActorRef, (Endpoint, SendProcessor)]()
private val watcher = system.actorOf(Props(new ProducerWatcher(this)))
private def registerWatch(actorRef: ActorRef) {
watcher ! RegisterProducer(actorRef)
}
/**
* For internal use only.
* Unregisters <code>Endpoint</code> and <code>SendProcessor</code> and stops the SendProcessor
*/
private[camel] def unregisterProducer(actorRef: ActorRef): Unit = {
// Terminated cannot be sent before the actor is created in the processing of system messages.
Option(camelObjects.remove(actorRef)).foreach {
case (_, processor)
try {
processor.stop()
system.eventStream.publish(EndpointDeActivated(actorRef))
} catch {
case NonFatal(e) system.eventStream.publish(EndpointFailedToDeActivate(actorRef, e))
}
}
}
/**
* For internal use only.
* Creates <code>Endpoint</code> and <code>SendProcessor</code> and associates the actorRef to these.
* @param actorRef the actorRef of the <code>Producer</code> actor.
* @param endpointUri the endpoint Uri of the producer
* @return <code>Endpoint</code> and <code>SendProcessor</code> registered for the actorRef
*/
private[camel] def registerProducer(actorRef: ActorRef, endpointUri: String): (Endpoint, SendProcessor) = {
try {
val endpoint = context.getEndpoint(endpointUri)
val processor = new SendProcessor(endpoint)
camelObjects.putIfAbsent(actorRef, (endpoint, processor)) match {
case null
processor.start()
registerWatch(actorRef)
system.eventStream.publish(EndpointActivated(actorRef))
(endpoint, processor)
case prev prev
}
} catch {
case NonFatal(e) {
system.eventStream.publish(EndpointFailedToActivate(actorRef, e))
// can't return null to the producer actor, so blow up actor in initialization.
throw e
}
}
}
}

View file

@ -0,0 +1,261 @@
/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camel.internal.component
import java.util.{ Map JMap }
import org.apache.camel._
import org.apache.camel.impl.{ DefaultProducer, DefaultEndpoint, DefaultComponent }
import akka.actor._
import akka.pattern._
import scala.reflect.BeanProperty
import akka.util.duration._
import java.util.concurrent.{ TimeoutException, CountDownLatch }
import akka.camel.internal.CamelExchangeAdapter
import akka.util.{ NonFatal, Duration, Timeout }
import akka.camel.{ DefaultConsumerConfig, ConsumerConfig, Camel, Ack, Failure CamelFailure, CamelMessage }
/**
* For internal use only.
* Camel component for sending messages to and receiving replies from (untyped) actors.
*
* @see akka.camel.component.ActorEndpoint
* @see akka.camel.component.ActorProducer
*
* @author Martin Krasser
*/
private[camel] class ActorComponent(camel: Camel) extends DefaultComponent {
def createEndpoint(uri: String, remaining: String, parameters: JMap[String, Object]): ActorEndpoint = {
val path = ActorEndpointPath.fromCamelPath(remaining)
new ActorEndpoint(uri, this, path, camel)
}
}
/**
* For internal use only.
* The ActorEndpoint is a Camel Endpoint that is used to receive messages from Camel through the ActorComponent
* Actors are referenced using <code>actor</code> endpoint URIs of the following format:
* <code>actor://path:[actorPath]?[options]%s</code>,
* where <code>[actorPath]</code> refers to the Actor Path to the Actor.
*
* @see akka.camel.component.ActorComponent
* @see akka.camel.component.ActorProducer
*
* @author Martin Krasser
*/
private[camel] class ActorEndpoint(uri: String,
comp: ActorComponent,
val path: ActorEndpointPath,
camel: Camel) extends DefaultEndpoint(uri, comp) with ActorEndpointConfig {
/**
*
* The ActorEndpoint right now only supports receiving messages from Camel.
* The createProducer method (not to be confused with a producer actor) is used to send messages into the endpoint.
* The ActorComponent is only there to send to actors registered through an actor endpoint URI.
* You can use an actor as an endpoint to send to in a camel route (as in, a Camel Consumer Actor). so from(someuri) to (actoruri), but not 'the other way around'.
* Supporting createConsumer would mean that messages are consumed from an Actor endpoint in a route, and an Actor is not necessarily a producer of messages
* [[akka.camel.Producer]] Actors can be used for sending messages to some other uri/ component type registered in Camel.
* @throws UnsupportedOperationException this method is not supported
*/
def createConsumer(processor: Processor): org.apache.camel.Consumer =
throw new UnsupportedOperationException("actor consumer not supported yet")
/**
* Creates a new ActorProducer instance initialized with this endpoint.
*/
def createProducer: ActorProducer = new ActorProducer(this, camel)
/**
* Returns true.
*/
def isSingleton: Boolean = true
}
private[camel] trait ActorEndpointConfig {
def path: ActorEndpointPath
@BeanProperty var replyTimeout: Duration = 1 minute
/**
* TODO fix it
* Whether to auto-acknowledge one-way message exchanges with (untyped) actors. This is
* set via the <code>blocking=true|false</code> endpoint URI parameter. Default value is
* <code>true</code>. When set to <code>true</code> consumer actors need to additionally
* call <code>Consumer.ack</code> within <code>Actor.receive</code>.
*/
@BeanProperty var autoack: Boolean = true
}
/**
* Sends the in-message of an exchange to an untyped actor, identified by an [[akka.camel.internal.component.ActorEndPoint]]
*
* @see akka.camel.component.ActorComponent
* @see akka.camel.component.ActorEndpoint
*
* @author Martin Krasser
*/
private[camel] class ActorProducer(val endpoint: ActorEndpoint, camel: Camel) extends DefaultProducer(endpoint) with AsyncProcessor {
/**
* Processes the exchange.
* Calls the asynchronous version of the method and waits for the result (blocking)
* @param exchange the exchange to process
*/
def process(exchange: Exchange) { processExchangeAdapter(new CamelExchangeAdapter(exchange)) }
/**
* Processes the message exchange. the caller supports having the exchange asynchronously processed.
* If there was a failure processing then the caused Exception would be set on the Exchange.
*
* @param exchange the message exchange
* @param callback the AsyncCallback will be invoked when the processing of the exchange is completed.
* If the exchange is completed synchronously, then the callback is also invoked synchronously.
* The callback should therefore be careful of starting recursive loop.
* @return (doneSync) true to continue execute synchronously, false to continue being executed asynchronously
*/
def process(exchange: Exchange, callback: AsyncCallback): Boolean = { processExchangeAdapter(new CamelExchangeAdapter(exchange), callback) }
/**
* For internal use only. Processes the [[akka.camel.internal.CamelExchangeAdapter]]
* @param exchange the [[akka.camel.internal.CamelExchangeAdapter]]
*/
private[camel] def processExchangeAdapter(exchange: CamelExchangeAdapter) {
val isDone = new CountDownLatch(1)
processExchangeAdapter(exchange, new AsyncCallback { def done(doneSync: Boolean) { isDone.countDown() } })
isDone.await() // this should never wait forever as the process(exchange, callback) method guarantees that.
}
/**
* For internal use only. Processes the [[akka.camel.internal.CamelExchangeAdapter]].
* This method is blocking when the exchange is inOnly. The method returns true if it executed synchronously/blocking.
* @param exchange the [[akka.camel.internal.CamelExchangeAdapter]]
* @param callback the callback
* @return (doneSync) true to continue execute synchronously, false to continue being executed asynchronously
*/
private[camel] def processExchangeAdapter(exchange: CamelExchangeAdapter, callback: AsyncCallback): Boolean = {
// these notify methods are just a syntax sugar
def notifyDoneSynchronously[A](a: A = null) = callback.done(true)
def notifyDoneAsynchronously[A](a: A = null) = callback.done(false)
def message = messageFor(exchange)
if (exchange.isOutCapable) { //InOut
sendAsync(message, onComplete = forwardResponseTo(exchange) andThen notifyDoneAsynchronously)
} else { // inOnly
if (endpoint.autoack) { //autoAck
fireAndForget(message, exchange)
notifyDoneSynchronously()
true // done sync
} else { //manualAck
sendAsync(message, onComplete = forwardAckTo(exchange) andThen notifyDoneAsynchronously)
}
}
}
private def forwardResponseTo(exchange: CamelExchangeAdapter): PartialFunction[Either[Throwable, Any], Unit] = {
case Right(failure: CamelFailure) exchange.setFailure(failure);
case Right(msg) exchange.setResponse(CamelMessage.canonicalize(msg))
case Left(e: TimeoutException) exchange.setFailure(CamelFailure(new TimeoutException("Failed to get response from the actor [%s] within timeout [%s]. Check replyTimeout and blocking settings [%s]" format (endpoint.path, endpoint.replyTimeout, endpoint))))
case Left(throwable) exchange.setFailure(CamelFailure(throwable))
}
private def forwardAckTo(exchange: CamelExchangeAdapter): PartialFunction[Either[Throwable, Any], Unit] = {
case Right(Ack) { /* no response message to set */ }
case Right(failure: CamelFailure) exchange.setFailure(failure)
case Right(msg) exchange.setFailure(CamelFailure(new IllegalArgumentException("Expected Ack or Failure message, but got: [%s] from actor [%s]" format (msg, endpoint.path))))
case Left(e: TimeoutException) exchange.setFailure(CamelFailure(new TimeoutException("Failed to get Ack or Failure response from the actor [%s] within timeout [%s]. Check replyTimeout and blocking settings [%s]" format (endpoint.path, endpoint.replyTimeout, endpoint))))
case Left(throwable) exchange.setFailure(CamelFailure(throwable))
}
private def sendAsync(message: CamelMessage, onComplete: PartialFunction[Either[Throwable, Any], Unit]): Boolean = {
try {
val actor = actorFor(endpoint.path)
val future = actor.ask(message)(new Timeout(endpoint.replyTimeout))
future.onComplete(onComplete)
} catch {
case NonFatal(e) onComplete(Left(e))
}
false // Done async
}
private def fireAndForget(message: CamelMessage, exchange: CamelExchangeAdapter) {
try {
actorFor(endpoint.path) ! message
} catch {
case e exchange.setFailure(new CamelFailure(e))
}
}
private[this] def actorFor(path: ActorEndpointPath): ActorRef =
path.findActorIn(camel.system) getOrElse (throw new ActorNotRegisteredException(path.actorPath))
private[this] def messageFor(exchange: CamelExchangeAdapter) =
exchange.toRequestMessage(Map(CamelMessage.MessageExchangeId -> exchange.getExchangeId))
}
/**
* Thrown to indicate that an actor referenced by an endpoint URI cannot be
* found in the Actor.registry.
*
* @author Martin Krasser
*/
class ActorNotRegisteredException(uri: String) extends RuntimeException {
override def getMessage = "Actor [%s] doesn't exist" format uri
}
/**
* For internal use only.
*/
private[camel] object DurationTypeConverter extends CamelTypeConverter {
def convertTo[T](`type`: Class[T], value: AnyRef) = {
Duration(value.toString).asInstanceOf[T]
}
def toString(duration: Duration) = duration.toNanos + " nanos"
}
/**
* For internal use only.
*/
private[camel] abstract class CamelTypeConverter extends TypeConverter {
def convertTo[T](`type`: Class[T], exchange: Exchange, value: AnyRef) = convertTo(`type`, value)
def mandatoryConvertTo[T](`type`: Class[T], value: AnyRef) = convertTo(`type`, value)
def mandatoryConvertTo[T](`type`: Class[T], exchange: Exchange, value: AnyRef) = convertTo(`type`, value)
}
/**
* For internal use only. An endpoint to an <code>ActorRef</code>
* @param actorPath the path to the actor
*/
private[camel] case class ActorEndpointPath private (actorPath: String) {
require(actorPath != null)
require(actorPath.length() > 0)
def toCamelPath(config: ConsumerConfig = DefaultConsumerConfig): String = "actor://path:%s?%s" format (actorPath, config.toCamelParameters)
def findActorIn(system: ActorSystem): Option[ActorRef] = {
val ref = system.actorFor(actorPath)
if (ref.isTerminated) None else Some(ref)
}
}
/**
* For internal use only. Companion of <code>ActorEndpointPath</code>
*/
private[camel] object ActorEndpointPath {
def apply(actorRef: ActorRef) = new ActorEndpointPath(actorRef.path.toString)
/**
* Expects path in a format: path:%s
*/
def fromCamelPath(camelPath: String) = camelPath match {
case id if id startsWith "path:" new ActorEndpointPath(id substring 5)
case _ throw new IllegalArgumentException("Invalid path: [%s] - should be path:<actorPath>" format camelPath)
}
}

View file

@ -0,0 +1,45 @@
/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camel.javaapi
import akka.actor.UntypedActor
import akka.camel._
import org.apache.camel.{ ProducerTemplate, CamelContext }
/**
* Java-friendly Consumer.
*
* @see UntypedConsumerActor
* @see RemoteUntypedConsumerActor
*
* @author Martin Krasser
*/
trait UntypedConsumer extends Consumer { self: UntypedActor
final def endpointUri = getEndpointUri
/**
* Returns the Camel endpoint URI to consume messages from.
*/
def getEndpointUri(): String
}
/**
* 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 {
/**
* Returns the [[org.apache.camel.CamelContext]]
* @return the CamelContext
*/
protected def getCamelContext: CamelContext = camelContext
/**
* Returns the [[org.apache.camel.ProducerTemplate]]
* @return the ProducerTemplate
*/
protected def getProducerTemplate: ProducerTemplate = camel.template
}

View file

@ -0,0 +1,72 @@
/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camel.javaapi
import akka.actor.UntypedActor
import akka.camel._
import org.apache.camel.{ CamelContext, ProducerTemplate }
/**
* 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 ProducerSupport {
/**
* 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.
*/
def onReceiveBeforeProduce(message: AnyRef): AnyRef = message
/**
* Called after a response was received from the endpoint specified by <code>endpointUri</code>. The
* response is passed as argument. By default, this method sends the response back to the original sender
* if <code>oneway</code> is <code>false</code>. If <code>oneway</code> is <code>true</code>, nothing is
* done. This method may be overridden by subclasses (e.g. to forward responses to another actor).
*/
def onReceiveAfterProduce(message: AnyRef): Unit = super.receiveAfterProduce(message)
final override def receiveBeforeProduce = {
case msg: AnyRef onReceiveBeforeProduce(msg)
}
final override def receiveAfterProduce = {
case msg: AnyRef onReceiveAfterProduce(msg)
}
final override def endpointUri = getEndpointUri
final override def oneway = isOneway
/**
* 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
/**
* Returns the <code>CamelContext</code>.
*/
def getCamelContext(): CamelContext = camel.context
/**
* Returns the <code>ProducerTemplate</code>.
*/
def getProducerTemplate(): ProducerTemplate = camel.template
}

View file

@ -0,0 +1,43 @@
/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camelexamples
import akka.camel._
import akka.util.duration._
import akka.actor.{ Actor, OneForOneStrategy }
import akka.actor.SupervisorStrategy._
object ExamplesSupport {
val retry3xWithin1s = OneForOneStrategy(maxNrOfRetries = 3, withinTimeRange = 1 second) {
case _: Exception Restart
}
}
class SysOutConsumer extends Consumer {
override def activationTimeout = 10 seconds
def endpointUri = "file://data/input/CamelConsumer"
protected def receive = {
case msg: CamelMessage {
printf("Received '%s'\n", msg.bodyAs[String])
}
}
}
class TroubleMaker extends Consumer {
def endpointUri = "WRONG URI"
println("Trying to instantiate conumer with uri: " + endpointUri)
protected def receive = { case _ }
}
class SysOutActor(implicit camel: Camel) extends Actor {
implicit val camelContext = camel.context
protected def receive = {
case msg: CamelMessage {
printf("Received '%s'\n", msg.bodyAs[String])
}
}
}

View file

@ -0,0 +1 @@
This package is outside of akka.camel because we don't want to use private[camel] features in examples.

View file

@ -0,0 +1,24 @@
/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camelexamples
import java.io.FileWriter
object RichString {
implicit def toRichString(s: String): RichString = new RichString(s)
}
class RichString(s: String) {
def saveAs(fileName: String) = write(fileName, s)
def >>(fileName: String) = this.saveAs(fileName)
def <<(content: String) = write(s, content)
private[this] def write(fileName: String, content: String) {
val f = new FileWriter(fileName)
f.write(content)
f.close()
}
}

View file

@ -0,0 +1,21 @@
/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camelexamples
import akka.actor.{ Props, ActorSystem }
import RichString._
object _1_SimpleConsumer extends App {
val system = ActorSystem("test")
system.actorOf(Props[SysOutConsumer])
"data/input/CamelConsumer/file1.txt" << "test data " + math.random
Thread.sleep(2000)
system.shutdown()
}

View file

@ -0,0 +1,39 @@
/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camelexamples
import akka.actor.{ PoisonPill, Terminated, Props, ActorSystem, Actor }
import ExamplesSupport._
import RichString._
object SupervisedConsumersExample extends App {
val system = ActorSystem("test1")
system.actorOf(Props(new Actor {
context.watch(context.actorOf(Props[EndpointManager]))
protected def receive = {
case Terminated(ref) system.shutdown()
}
}))
"data/input/CamelConsumer/file1.txt" << "test data " + math.random
}
class EndpointManager extends Actor {
import context._
override def supervisorStrategy() = retry3xWithin1s
watch(actorOf(Props[SysOutConsumer]))
watch(actorOf(Props[TroubleMaker]))
protected def receive = {
case Terminated(ref) {
printf("Hey! One of the endpoints has died: %s. I am doing sepuku...\n", ref)
self ! PoisonPill
}
}
}

View file

@ -0,0 +1,31 @@
/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camelexamples
import org.apache.camel.builder.RouteBuilder
import akka.actor.{ Props, ActorSystem }
import akka.camel._
import RichString._
object _3_SimpleActorEndpoint extends App {
val system = ActorSystem("test")
val camel = CamelExtension(system)
val actor = system.actorOf(Props[SysOutActor])
camel.context.addRoutes(new RouteBuilder() {
def configure() {
from("file://data/input/CamelConsumer").to(actor)
}
})
"data/input/CamelConsumer/file1.txt" << "test data " + math.random
Thread.sleep(3000)
system.shutdown()
}

View file

@ -0,0 +1,11 @@
/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka
import org.apache.camel.model.ProcessorDefinition
package object camel {
implicit def toActorRouteDefinition(definition: ProcessorDefinition[_]) = new ActorRouteDefinition(definition)
}

View file

@ -1,46 +1,41 @@
/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camel;
import akka.japi.SideEffect;
import akka.actor.ActorRef;
import akka.actor.ActorSystem;
import akka.actor.Props;
import akka.util.FiniteDuration;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import static akka.actor.Actors.*;
import static akka.camel.CamelContextManager.*;
import static akka.camel.CamelServiceManager.*;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.*;
/**
* @author Martin Krasser
*/
public class ConsumerJavaTestBase {
@BeforeClass
public static void setUpBeforeClass() {
startCamelService();
}
static ActorSystem system = ActorSystem.create("test");
static Camel camel = (Camel) CamelExtension.get(system);
@AfterClass
public static void tearDownAfterClass() {
stopCamelService();
registry().local().shutdownAll();
system.shutdown();
}
@Test @Ignore // TODO: fix race
// org.apache.camel.CamelExchangeException: No consumers available
// on endpoint: Endpoint[direct://error-handler-test-java]
@Test
public void shouldHandleExceptionThrownByActorAndGenerateCustomResponse() {
getMandatoryService().awaitEndpointActivation(1, new SideEffect() {
public void apply() {
actorOf(SampleErrorHandlingConsumer.class);
}
});
String result = getMandatoryTemplate().requestBody("direct:error-handler-test-java", "hello", String.class);
ActorRef ref = system.actorOf(new Props().withCreator(SampleErrorHandlingConsumer.class));
camel.awaitActivation(ref, new FiniteDuration(1, TimeUnit.SECONDS));
String result = camel.template().requestBody("direct:error-handler-test-java", "hello", String.class);
assertEquals("error: hello", result);
}
}

View file

@ -1,109 +1,126 @@
/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camel;
import akka.actor.ActorSystem;
import akka.japi.Function;
import org.apache.camel.NoTypeConversionAvailableException;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import akka.camel.CamelContextManager;
import akka.camel.Message;
import akka.japi.Function;
import java.io.InputStream;
import java.util.*;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
/**
* @author Martin Krasser
*/
public class MessageJavaTestBase {
static Camel camel;
private static ActorSystem system;
private Map<String,Object> empty = new HashMap<String, Object>();
@BeforeClass
public static void setUpBeforeClass() {
CamelContextManager.init();
system = ActorSystem.create("test");
camel = (Camel) CamelExtension.get(system);
}
@AfterClass
public static void cleanup(){
system.shutdown();
}
CamelMessage message(Object body){ return new CamelMessage(body, new HashMap<String, Object>()); }
CamelMessage message(Object body, Map<String, Object> headers){ return new CamelMessage(body, headers); }
@Test public void shouldConvertDoubleBodyToString() {
assertEquals("1.4", new Message("1.4").getBodyAs(String.class));
assertEquals("1.4", message("1.4", empty).getBodyAs(String.class,camel.context()));
}
@Test(expected=NoTypeConversionAvailableException.class)
public void shouldThrowExceptionWhenConvertingDoubleBodyToInputStream() {
new Message(1.4).getBodyAs(InputStream.class);
message(1.4).getBodyAs(InputStream.class,camel.context());
}
@Test public void shouldReturnDoubleHeader() {
Message message = new Message("test" , createMap("test", 1.4));
CamelMessage message = message("test" , createMap("test", 1.4));
assertEquals(1.4, message.getHeader("test"));
}
@Test public void shouldConvertDoubleHeaderToString() {
Message message = new Message("test" , createMap("test", 1.4));
assertEquals("1.4", message.getHeaderAs("test", String.class));
CamelMessage message = message("test" , createMap("test", 1.4));
assertEquals("1.4", message.getHeaderAs("test", String.class,camel.context()));
}
@Test public void shouldReturnSubsetOfHeaders() {
Message message = new Message("test" , createMap("A", "1", "B", "2"));
CamelMessage message = message("test" , createMap("A", "1", "B", "2"));
assertEquals(createMap("B", "2"), message.getHeaders(createSet("B")));
}
@Test(expected=UnsupportedOperationException.class)
public void shouldReturnSubsetOfHeadersUnmodifiable() {
Message message = new Message("test" , createMap("A", "1", "B", "2"));
CamelMessage message = message("test" , createMap("A", "1", "B", "2"));
message.getHeaders(createSet("B")).put("x", "y");
}
@Test public void shouldReturnAllHeaders() {
Message message = new Message("test" , createMap("A", "1", "B", "2"));
CamelMessage message = message("test" , createMap("A", "1", "B", "2"));
assertEquals(createMap("A", "1", "B", "2"), message.getHeaders());
}
@Test(expected=UnsupportedOperationException.class)
public void shouldReturnAllHeadersUnmodifiable() {
Message message = new Message("test" , createMap("A", "1", "B", "2"));
CamelMessage message = message("test" , createMap("A", "1", "B", "2"));
message.getHeaders().put("x", "y");
}
@Test public void shouldTransformBodyAndPreserveHeaders() {
assertEquals(
new Message("ab", createMap("A", "1")),
new Message("a" , createMap("A", "1")).transformBody((Function<String, Object>) new TestTransformer()));
message("ab", createMap("A", "1")),
message("a" , createMap("A", "1")).mapBody(new TestTransformer()));
}
@Test public void shouldConvertBodyAndPreserveHeaders() {
assertEquals(
new Message("1.4", createMap("A", "1")),
new Message(1.4 , createMap("A", "1")).setBodyAs(String.class));
message("1.4", createMap("A", "1")),
message(1.4 , createMap("A", "1")).withBodyAs(String.class,camel.context()));
}
@Test public void shouldSetBodyAndPreserveHeaders() {
assertEquals(
new Message("test2" , createMap("A", "1")),
new Message("test1" , createMap("A", "1")).setBody("test2"));
message("test2" , createMap("A", "1")),
message("test1" , createMap("A", "1")).withBody("test2"));
}
@Test public void shouldSetHeadersAndPreserveBody() {
assertEquals(
new Message("test1" , createMap("C", "3")),
new Message("test1" , createMap("A", "1")).setHeaders(createMap("C", "3")));
message("test1" , createMap("C", "3")),
message("test1" , createMap("A", "1")).withHeaders(createMap("C", "3")));
}
@Test public void shouldAddHeaderAndPreserveBodyAndHeaders() {
assertEquals(
new Message("test1" , createMap("A", "1", "B", "2")),
new Message("test1" , createMap("A", "1")).addHeader("B", "2"));
message("test1" , createMap("A", "1", "B", "2")),
message("test1" , createMap("A", "1")).addHeader("B", "2"));
}
@Test public void shouldAddHeadersAndPreserveBodyAndHeaders() {
assertEquals(
new Message("test1" , createMap("A", "1", "B", "2")),
new Message("test1" , createMap("A", "1")).addHeaders(createMap("B", "2")));
message("test1" , createMap("A", "1", "B", "2")),
message("test1" , createMap("A", "1")).addHeaders(createMap("B", "2")));
}
@Test public void shouldRemoveHeadersAndPreserveBodyAndRemainingHeaders() {
assertEquals(
new Message("test1" , createMap("A", "1")),
new Message("test1" , createMap("A", "1", "B", "2")).removeHeader("B"));
message("test1" , createMap("A", "1")),
message("test1" , createMap("A", "1", "B", "2")).withoutHeader("B"));
}
private static Set<String> createSet(String... entries) {
@ -120,7 +137,7 @@ public class MessageJavaTestBase {
return map;
}
private static class TestTransformer implements Function<String, Object> {
private static class TestTransformer implements Function<String, String> {
public String apply(String param) {
return param + "b";
}

View file

@ -1,8 +1,15 @@
/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camel;
import akka.camel.javaapi.UntypedConsumerActor;
import akka.util.Duration;
import org.apache.camel.builder.Builder;
import org.apache.camel.model.ProcessorDefinition;
import org.apache.camel.model.RouteDefinition;
import scala.Option;
/**
* @author Martin Krasser
@ -13,22 +20,28 @@ public class SampleErrorHandlingConsumer extends UntypedConsumerActor {
return "direct:error-handler-test-java";
}
public boolean isBlocking() {
return true;
}
public void preStart() {
onRouteDefinition(new RouteDefinitionHandler() {
public ProcessorDefinition<?> onRouteDefinition(RouteDefinition rd) {
@Override
//TODO write test confirming this gets called in java
public ProcessorDefinition onRouteDefinition(RouteDefinition rd) {
return rd.onException(Exception.class).handled(true).transform(Builder.exceptionMessage()).end();
}
});
@Override
public Duration replyTimeout(){
return Duration.create(1, "second");
}
public void onReceive(Object message) throws Exception {
Message msg = (Message)message;
String body = msg.getBodyAs(String.class);
CamelMessage msg = (CamelMessage) message;
String body = msg.getBodyAs(String.class,this.getCamelContext());
throw new Exception(String.format("error: %s", body));
}
@Override
public void preRestart(Throwable reason, Option<Object> message){
getSender().tell(new Failure(reason));
}
}

View file

@ -1,3 +1,7 @@
/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camel;
import akka.actor.UntypedActor;

View file

@ -1,6 +1,10 @@
/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camel;
import akka.camel.UntypedConsumerActor;
import akka.camel.javaapi.UntypedConsumerActor;
/**
* @author Martin Krasser
@ -12,10 +16,10 @@ public class SampleUntypedConsumer extends UntypedConsumerActor {
}
public void onReceive(Object message) {
Message msg = (Message)message;
String body = msg.getBodyAs(String.class);
String header = msg.getHeaderAs("test", String.class);
sender.tell(String.format("%s %s", body, header));
CamelMessage msg = (CamelMessage)message;
String body = msg.getBodyAs(String.class, getCamelContext());
String header = msg.getHeaderAs("test", String.class,getCamelContext());
sender().tell(String.format("%s %s", body, header));
}
}

View file

@ -1,23 +0,0 @@
package 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.getBodyAs(String.class);
String header = msg.getHeaderAs("test", String.class);
sender.tell(String.format("%s %s", body, header));
}
}

View file

@ -1,5 +1,10 @@
/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camel;
import akka.camel.javaapi.UntypedProducerActor;
/**
* @author Martin Krasser
*/
@ -11,8 +16,8 @@ public class SampleUntypedForwardingProducer extends UntypedProducerActor {
@Override
public void onReceiveAfterProduce(Object message) {
Message msg = (Message)message;
String body = msg.getBodyAs(String.class);
CamelContextManager.getMandatoryTemplate().sendBody("direct:forward-test-1", body);
CamelMessage msg = (CamelMessage)message;
String body = msg.getBodyAs(String.class,getCamelContext());
getProducerTemplate().sendBody("direct:forward-test-1", body);
}
}

View file

@ -1,5 +1,11 @@
/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camel;
import akka.camel.javaapi.UntypedProducerActor;
/**
* @author Martin Krasser
*/

View file

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<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>
<root level="OFF">
<appender-ref ref="stdout"/>
</root>
</configuration>

View file

@ -0,0 +1,74 @@
/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camel
import org.scalatest.matchers.MustMatchers
import akka.util.duration._
import org.apache.camel.ProducerTemplate
import akka.actor._
import akka.util.Timeout
import TestSupport._
import org.scalatest.WordSpec
import akka.testkit.TestLatch
import akka.dispatch.Await
class ActivationIntegrationTest extends WordSpec with MustMatchers with SharedCamelSystem {
implicit val timeout = Timeout(10 seconds)
def template: ProducerTemplate = camel.template
def testActorWithEndpoint(uri: String): ActorRef = { system.actorOf(Props(new TestConsumer(uri))) }
"ActivationAware must be notified when endpoint is activated" in {
val actor = testActorWithEndpoint("direct:actor-1")
try {
camel.awaitActivation(actor, 1 second)
} catch {
case e: ActivationTimeoutException fail("Failed to get notification within 1 second")
}
template.requestBody("direct:actor-1", "test") must be("received test")
}
"ActivationAware must be notified when endpoint is de-activated" in {
val latch = TestLatch()
val actor = start(new Consumer {
def endpointUri = "direct:a3"
def receive = { case _ {} }
override def postStop() = {
super.postStop()
latch.countDown()
}
})
camel.awaitActivation(actor, 1 second)
system.stop(actor)
camel.awaitDeactivation(actor, 1 second)
Await.ready(latch, 1 second)
}
"ActivationAware must time out when waiting for endpoint de-activation for too long" in {
val actor = start(new TestConsumer("direct:a5"))
camel.awaitActivation(actor, 1 second)
intercept[DeActivationTimeoutException] {
camel.awaitDeactivation(actor, 1 millis)
}
}
"awaitActivation must fail if notification timeout is too short and activation is not complete yet" in {
val actor = testActorWithEndpoint("direct:actor-4")
intercept[ActivationTimeoutException] {
camel.awaitActivation(actor, 0 seconds)
}
}
class TestConsumer(uri: String) extends Consumer {
def endpointUri = uri
override def receive = {
case msg: CamelMessage sender ! "received " + msg.body
}
}
}

View file

@ -1,37 +0,0 @@
package akka.camel
import org.apache.camel.impl.{ DefaultProducerTemplate, DefaultCamelContext }
import org.junit.Test
import org.scalatest.junit.JUnitSuite
class CamelContextLifecycleTest extends JUnitSuite with CamelContextLifecycle {
@Test
def shouldManageCustomCamelContext {
assert(context === None)
assert(template === None)
intercept[IllegalStateException] { mandatoryContext }
intercept[IllegalStateException] { mandatoryTemplate }
val ctx = new TestCamelContext
assert(ctx.isStreamCaching === false)
init(ctx)
assert(mandatoryContext.isStreamCaching === true)
assert(!mandatoryContext.asInstanceOf[TestCamelContext].isStarted)
assert(mandatoryTemplate.asInstanceOf[DefaultProducerTemplate].isStarted)
start
assert(mandatoryContext.asInstanceOf[TestCamelContext].isStarted)
assert(mandatoryTemplate.asInstanceOf[DefaultProducerTemplate].isStarted)
stop
assert(!mandatoryContext.asInstanceOf[TestCamelContext].isStarted)
assert(!mandatoryTemplate.asInstanceOf[DefaultProducerTemplate].isStarted)
}
class TestCamelContext extends DefaultCamelContext
}

View file

@ -1,95 +1,96 @@
/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camel
import org.apache.camel.impl.{ DefaultCamelContext, DefaultExchange }
import org.apache.camel.ExchangePattern
import org.junit.Test
import org.scalatest.junit.JUnitSuite
import internal.CamelExchangeAdapter
import org.apache.camel.impl.DefaultExchange
import org.apache.camel.{ Exchange, ExchangePattern }
import akka.camel.TestSupport.{ SharedCamelSystem, MessageSugar }
import org.scalatest.FunSuite
class CamelExchangeAdapterTest extends JUnitSuite {
import CamelMessageConversion.toExchangeAdapter
class CamelExchangeAdapterTest extends FunSuite with SharedCamelSystem with MessageSugar {
@Test
def shouldSetInMessageFromRequestMessage = {
val e1 = sampleInOnly.fromRequestMessage(Message("x"))
//TODO: Get rid of implicit.
// It is here, as was easier to add this implicit than to rewrite the whole test...
implicit def exchangeToAdapter(e: Exchange) = new CamelExchangeAdapter(e)
test("mustSetInMessageFromRequestMessage") {
val e1 = sampleInOnly
e1.setRequest(Message("x"))
assert(e1.getIn.getBody === "x")
val e2 = sampleInOut.fromRequestMessage(Message("y"))
val e2 = sampleInOut
e2.setRequest(Message("y"))
assert(e2.getIn.getBody === "y")
}
@Test
def shouldSetOutMessageFromResponseMessage = {
val e1 = sampleInOut.fromResponseMessage(Message("y"))
test("mustSetOutMessageFromResponseMessage") {
val e1 = sampleInOut
e1.setResponse(Message("y"))
assert(e1.getOut.getBody === "y")
}
@Test
def shouldSetInMessageFromResponseMessage = {
val e1 = sampleInOnly.fromResponseMessage(Message("x"))
test("mustSetInMessageFromResponseMessage") {
val e1 = sampleInOnly
e1.setResponse(Message("x"))
assert(e1.getIn.getBody === "x")
}
@Test
def shouldSetExceptionFromFailureMessage = {
val e1 = sampleInOnly.fromFailureMessage(Failure(new Exception("test1")))
test("mustSetExceptionFromFailureMessage") {
val e1 = sampleInOnly
e1.setFailure(Failure(new Exception("test1")))
assert(e1.getException.getMessage === "test1")
val e2 = sampleInOut.fromFailureMessage(Failure(new Exception("test2")))
val e2 = sampleInOut
e2.setFailure(Failure(new Exception("test2")))
assert(e2.getException.getMessage === "test2")
}
@Test
def shouldCreateRequestMessageFromInMessage = {
test("mustCreateRequestMessageFromInMessage") {
val m = sampleInOnly.toRequestMessage
assert(m === Message("test-in", Map("key-in" -> "val-in")))
}
@Test
def shouldCreateResponseMessageFromInMessage = {
test("mustCreateResponseMessageFromInMessage") {
val m = sampleInOnly.toResponseMessage
assert(m === Message("test-in", Map("key-in" -> "val-in")))
}
@Test
def shouldCreateResponseMessageFromOutMessage = {
test("mustCreateResponseMessageFromOutMessage") {
val m = sampleInOut.toResponseMessage
assert(m === Message("test-out", Map("key-out" -> "val-out")))
}
@Test
def shouldCreateFailureMessageFromExceptionAndInMessage = {
test("mustCreateFailureMessageFromExceptionAndInMessage") {
val e1 = sampleInOnly
e1.setException(new Exception("test1"))
assert(e1.toFailureMessage.cause.getMessage === "test1")
assert(e1.toFailureMessage.headers("key-in") === "val-in")
}
@Test
def shouldCreateFailureMessageFromExceptionAndOutMessage = {
test("mustCreateFailureMessageFromExceptionAndOutMessage") {
val e1 = sampleInOut
e1.setException(new Exception("test2"))
assert(e1.toFailureMessage.cause.getMessage === "test2")
assert(e1.toFailureMessage.headers("key-out") === "val-out")
}
@Test
def shouldCreateRequestMessageFromInMessageWithAdditionalHeader = {
test("mustCreateRequestMessageFromInMessageWithAdditionalHeader") {
val m = sampleInOnly.toRequestMessage(Map("x" -> "y"))
assert(m === Message("test-in", Map("key-in" -> "val-in", "x" -> "y")))
}
@Test
def shouldCreateResponseMessageFromInMessageWithAdditionalHeader = {
test("mustCreateResponseMessageFromInMessageWithAdditionalHeader") {
val m = sampleInOnly.toResponseMessage(Map("x" -> "y"))
assert(m === Message("test-in", Map("key-in" -> "val-in", "x" -> "y")))
}
@Test
def shouldCreateResponseMessageFromOutMessageWithAdditionalHeader = {
test("mustCreateResponseMessageFromOutMessageWithAdditionalHeader") {
val m = sampleInOut.toResponseMessage(Map("x" -> "y"))
assert(m === Message("test-out", Map("key-out" -> "val-out", "x" -> "y")))
}
@Test
def shouldCreateFailureMessageFromExceptionAndInMessageWithAdditionalHeader = {
test("mustCreateFailureMessageFromExceptionAndInMessageWithAdditionalHeader") {
val e1 = sampleInOnly
e1.setException(new Exception("test1"))
assert(e1.toFailureMessage.cause.getMessage === "test1")
@ -98,8 +99,7 @@ class CamelExchangeAdapterTest extends JUnitSuite {
assert(headers("x") === "y")
}
@Test
def shouldCreateFailureMessageFromExceptionAndOutMessageWithAdditionalHeader = {
test("mustCreateFailureMessageFromExceptionAndOutMessageWithAdditionalHeader") {
val e1 = sampleInOut
e1.setException(new Exception("test2"))
assert(e1.toFailureMessage.cause.getMessage === "test2")
@ -112,7 +112,7 @@ class CamelExchangeAdapterTest extends JUnitSuite {
private def sampleInOut = sampleExchange(ExchangePattern.InOut)
private def sampleExchange(pattern: ExchangePattern) = {
val exchange = new DefaultExchange(new DefaultCamelContext)
val exchange = new DefaultExchange(camel.context)
exchange.getIn.setBody("test-in")
exchange.getOut.setBody("test-out")
exchange.getIn.setHeader("key-in", "val-in")

View file

@ -1,40 +0,0 @@
package akka.camel
import org.apache.camel.impl.DefaultMessage
import org.junit.Test
import org.scalatest.junit.JUnitSuite
class CamelMessageAdapterTest extends JUnitSuite {
import CamelMessageConversion.toMessageAdapter
@Test
def shouldOverwriteBodyAndAddHeader = {
val cm = sampleMessage.fromMessage(Message("blah", Map("key" -> "baz")))
assert(cm.getBody === "blah")
assert(cm.getHeader("foo") === "bar")
assert(cm.getHeader("key") === "baz")
}
@Test
def shouldCreateMessageWithBodyAndHeader = {
val m = sampleMessage.toMessage
assert(m.body === "test")
assert(m.headers("foo") === "bar")
}
@Test
def shouldCreateMessageWithBodyAndHeaderAndCustomHeader = {
val m = sampleMessage.toMessage(Map("key" -> "baz"))
assert(m.body === "test")
assert(m.headers("foo") === "bar")
assert(m.headers("key") === "baz")
}
private[camel] def sampleMessage = {
val message = new DefaultMessage
message.setBody("test")
message.setHeader("foo", "bar")
message
}
}

View file

@ -0,0 +1,46 @@
/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camel
import org.apache.camel.impl.{ DefaultExchange, DefaultMessage }
import akka.camel.TestSupport.SharedCamelSystem
import org.scalatest.matchers.MustMatchers
import org.scalatest.WordSpec
//TODO merge it with MessageScalaTest
class CamelMessageTest extends MustMatchers with WordSpec with SharedCamelSystem {
"CamelMessage" must {
"overwrite body and add header" in {
val msg = sampleMessage
CamelMessage("blah", Map("key" -> "baz")).copyContentTo(msg)
assert(msg.getBody === "blah")
assert(msg.getHeader("foo") === "bar")
assert(msg.getHeader("key") === "baz")
}
"create message with body and header" in {
val m = CamelMessage.from(sampleMessage)
assert(m.body === "test")
assert(m.headers("foo") === "bar")
}
"create message with body and header and custom header" in {
val m = CamelMessage.from(sampleMessage, Map("key" -> "baz"))
assert(m.body === "test")
assert(m.headers("foo") === "bar")
assert(m.headers("key") === "baz")
}
}
private[camel] def sampleMessage = {
val message = new DefaultMessage
message.setBody("test")
message.setHeader("foo", "bar")
message.setExchange(new DefaultExchange(camel.context))
message
}
}

View file

@ -1,62 +0,0 @@
package akka.camel
import org.scalatest.{ BeforeAndAfterAll, WordSpec }
import org.scalatest.matchers.MustMatchers
import akka.actor.Actor
/**
* @author Martin Krasser
*/
class CamelServiceManagerTest extends WordSpec with BeforeAndAfterAll with MustMatchers {
override def afterAll = {
CamelServiceManager.stopCamelService
Actor.registry.local.shutdownAll
}
"A CamelServiceManager" when {
"the startCamelService method been has been called" must {
"have registered the started CamelService instance" in {
val service = CamelServiceManager.startCamelService
CamelServiceManager.mandatoryService must be theSameInstanceAs (service)
}
}
"the stopCamelService method been has been called" must {
"have unregistered the current CamelService instance" in {
val service = CamelServiceManager.stopCamelService
CamelServiceManager.service must be(None)
}
}
}
"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.mandatoryService must be theSameInstanceAs (service)
}
}
"the current CamelService instance has been stopped externally" must {
"have unregistered the current CamelService instance" in {
service.stop
CamelServiceManager.service must be(None)
}
}
}
"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 }
}
}
}
}

View file

@ -1,78 +0,0 @@
package akka.camel
import java.util.concurrent.CountDownLatch
import collection.mutable.Buffer
import akka.actor.Actor
object CamelTestSupport {
type Handler = PartialFunction[Any, Any]
trait TestActor extends Actor {
def receive = {
case msg {
handler(msg)
}
}
def handler: Handler
}
class Tester1 extends TestActor with Retain with Countdown {
def handler = retain andThen countdown
}
class Tester2 extends TestActor with Respond {
def handler = respond
}
class Tester3 extends TestActor with Noop {
def handler = noop
}
trait Countdown { this: Actor
var latch: CountDownLatch = new CountDownLatch(0)
def countdown: Handler = {
case SetExpectedMessageCount(num) {
latch = new CountDownLatch(num)
sender ! latch
}
case msg latch.countDown
}
}
trait Respond { this: Actor
def respond: Handler = {
case msg: Message sender ! response(msg)
}
def response(msg: Message): Any = "Hello %s" format msg.body
}
trait Retain { this: Actor
val messages = Buffer[Any]()
def retain: Handler = {
case GetRetainedMessage sender ! messages.last
case GetRetainedMessages(p) sender ! messages.filter(p).toList
case msg {
messages += msg
msg
}
}
}
trait Noop { this: Actor
def noop: Handler = {
case msg msg
}
}
case class SetExpectedMessageCount(num: Int)
case class GetRetainedMessage()
case class GetRetainedMessages(p: Any Boolean) {
def this() = this(_ true)
}
}

View file

@ -0,0 +1,171 @@
/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camel
import akka.actor._
import org.scalatest.matchers.MustMatchers
import akka.util.duration._
import TestSupport._
import org.scalatest.WordSpec
import org.apache.camel.model.RouteDefinition
import org.apache.camel.builder.Builder
import org.apache.camel.{ FailedToCreateRouteException, CamelExecutionException }
import java.util.concurrent.{ ExecutionException, TimeUnit, TimeoutException }
import akka.testkit.TestLatch
import akka.dispatch.Await
class ConsumerIntegrationTest extends WordSpec with MustMatchers with NonSharedCamelSystem {
private val defaultTimeout = 10
"Consumer must throw FailedToCreateRouteException, while awaiting activation, if endpoint is invalid" in {
val actorRef = system.actorOf(Props(new TestActor(uri = "some invalid uri")))
intercept[FailedToCreateRouteException] {
camel.awaitActivation(actorRef, timeout = defaultTimeout seconds)
}
}
"Consumer must support in-out messaging" in {
start(new Consumer {
def endpointUri = "direct:a1"
def receive = {
case m: CamelMessage sender ! "received " + m.bodyAs[String]
}
})
camel.sendTo("direct:a1", msg = "some message") must be("received some message")
}
"Consumer must time-out if consumer is slow" in {
val SHORT_TIMEOUT = 10 millis
val LONG_WAIT = 200 millis
start(new Consumer {
override def replyTimeout = SHORT_TIMEOUT
def endpointUri = "direct:a3"
def receive = { case _ { Thread.sleep(LONG_WAIT.toMillis); sender ! "done" } }
})
val exception = intercept[CamelExecutionException] {
camel.sendTo("direct:a3", msg = "some msg 3")
}
exception.getCause.getClass must be(classOf[TimeoutException])
}
"Consumer must process messages even after actor restart" in {
val restarted = TestLatch()
val consumer = start(new Consumer {
def endpointUri = "direct:a2"
def receive = {
case "throw" throw new Exception
case m: CamelMessage sender ! "received " + m.bodyAs[String]
}
override def postRestart(reason: Throwable) {
restarted.countDown()
}
})
consumer ! "throw"
Await.ready(restarted, defaultTimeout seconds)
val response = camel.sendTo("direct:a2", msg = "xyz")
response must be("received xyz")
}
"Consumer must unregister itself when stopped" in {
val consumer = start(new TestActor())
camel.awaitActivation(consumer, defaultTimeout seconds)
camel.routeCount must be > (0)
system.stop(consumer)
camel.awaitDeactivation(consumer, defaultTimeout seconds)
camel.routeCount must be(0)
}
"Error passing consumer supports error handling through route modification" in {
start(new ErrorThrowingConsumer("direct:error-handler-test") with ErrorPassing {
override def onRouteDefinition(rd: RouteDefinition) = {
rd.onException(classOf[Exception]).handled(true).transform(Builder.exceptionMessage).end
}
})
camel.sendTo("direct:error-handler-test", msg = "hello") must be("error: hello")
}
"Error passing consumer supports redelivery through route modification" in {
start(new FailingOnceConsumer("direct:failing-once-concumer") with ErrorPassing {
override def onRouteDefinition(rd: RouteDefinition) = {
rd.onException(classOf[Exception]).maximumRedeliveries(1).end
}
})
camel.sendTo("direct:failing-once-concumer", msg = "hello") must be("accepted: hello")
}
"Consumer supports manual Ack" in {
start(new ManualAckConsumer() {
def endpointUri = "direct:manual-ack"
def receive = { case _ sender ! Ack }
})
camel.template.asyncSendBody("direct:manual-ack", "some message").get(defaultTimeout, TimeUnit.SECONDS) must be(null) //should not timeout
}
"Consumer handles manual Ack failure" in {
val someException = new Exception("e1")
start(new ManualAckConsumer() {
def endpointUri = "direct:manual-ack"
def receive = { case _ sender ! Failure(someException) }
})
intercept[ExecutionException] {
camel.template.asyncSendBody("direct:manual-ack", "some message").get(defaultTimeout, TimeUnit.SECONDS)
}.getCause.getCause must be(someException)
}
"Consumer should time-out, if manual Ack not received within replyTimeout and should give a human readable error message" in {
start(new ManualAckConsumer() {
override def replyTimeout = 10 millis
def endpointUri = "direct:manual-ack"
def receive = { case _ }
})
intercept[ExecutionException] {
camel.template.asyncSendBody("direct:manual-ack", "some message").get(defaultTimeout, TimeUnit.SECONDS)
}.getCause.getCause.getMessage must include("Failed to get Ack")
}
}
class ErrorThrowingConsumer(override val endpointUri: String) extends Consumer {
def receive = {
case msg: CamelMessage throw new Exception("error: %s" format msg.body)
}
}
class FailingOnceConsumer(override val endpointUri: String) extends Consumer {
def receive = {
case msg: CamelMessage
if (msg.headerAs[Boolean]("CamelRedelivered").getOrElse(false))
sender ! ("accepted: %s" format msg.body)
else
throw new Exception("rejected: %s" format msg.body)
}
}
class TestActor(uri: String = "file://target/abcde") extends Consumer {
def endpointUri = uri
def receive = { case _ /* do nothing */ }
}
trait ErrorPassing {
self: Actor
final override def preRestart(reason: Throwable, message: Option[Any]) {
sender ! Failure(reason)
}
}
trait ManualAckConsumer extends Consumer {
override def autoack = false
}

View file

@ -1,3 +1,7 @@
/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camel
import org.scalatest.junit.JUnitSuite

View file

@ -1,61 +0,0 @@
package akka.camel
import java.util.concurrent.{ CountDownLatch, TimeUnit }
import org.junit.{ Before, After, Test }
import org.scalatest.junit.JUnitSuite
import akka.actor._
import akka.actor.Actor._
import akka.camel.CamelTestSupport.{ SetExpectedMessageCount SetExpectedTestMessageCount, _ }
import akka.dispatch.Await
class ConsumerPublishRequestorTest extends JUnitSuite {
import ConsumerPublishRequestorTest._
var publisher: ActorRef = _
var requestor: ActorRef = _
var consumer: LocalActorRef = _
@Before
def setUp{
publisher = actorOf(Props(new ConsumerPublisherMock)
requestor = actorOf(Props(new ConsumerPublishRequestor)
requestor ! InitPublishRequestor(publisher)
consumer = actorOf(Props(new Actor with Consumer {
def endpointUri = "mock:test"
protected def receive = null
}).asInstanceOf[LocalActorRef]
}
@After
def tearDown = {
Actor.registry.removeListener(requestor);
Actor.registry.local.shutdownAll
}
@Test
def shouldReceiveOneConsumerRegisteredEvent = {
val latch = Await.result((publisher ? SetExpectedTestMessageCount(1)).mapTo[CountDownLatch], 5 seconds)
requestor ! ActorRegistered(consumer.address, consumer)
assert(latch.await(5000, TimeUnit.MILLISECONDS))
assert(Await.result(publisher ? GetRetainedMessage, 5 seconds) ===
ConsumerActorRegistered(consumer, consumer.underlyingActorInstance.asInstanceOf[Consumer]))
}
@Test
def shouldReceiveOneConsumerUnregisteredEvent = {
val latch = Await.result((publisher ? SetExpectedTestMessageCount(1)).mapTo[CountDownLatch], 5 seconds)
requestor ! ActorUnregistered(consumer.address, consumer)
assert(latch.await(5000, TimeUnit.MILLISECONDS))
assert(Await.result(publisher ? GetRetainedMessage, 5 seconds) ===
ConsumerActorUnregistered(consumer, consumer.underlyingActorInstance.asInstanceOf[Consumer]))
}
}
object ConsumerPublishRequestorTest {
class ConsumerPublisherMock extends TestActor with Retain with Countdown {
def handler = retain andThen countdown
}
}

View file

@ -1,72 +0,0 @@
package akka.camel
import org.junit.Test
import org.scalatest.junit.JUnitSuite
import akka.actor.{ ActorRef, Actor, LocalActorRef }
class ConsumerRegisteredTest extends JUnitSuite {
import ConsumerRegisteredTest._
@Test
def shouldCreateSomeNonBlockingPublishRequestFromConsumer = {
val c = Actor.actorOf(Props[ConsumerActor1]
val event = ConsumerActorRegistered.eventFor(c)
assert(event === Some(ConsumerActorRegistered(c, consumerOf(c))))
}
@Test
def shouldCreateSomeBlockingPublishRequestFromConsumer = {
val c = Actor.actorOf(Props[ConsumerActor2]
val event = ConsumerActorRegistered.eventFor(c)
assert(event === Some(ConsumerActorRegistered(c, consumerOf(c))))
}
@Test
def shouldCreateNoneFromConsumer = {
val event = ConsumerActorRegistered.eventFor(Actor.actorOf(Props[PlainActor])
assert(event === None)
}
@Test
def shouldCreateSomeNonBlockingPublishRequestFromUntypedConsumer = {
val uc = Actor.actorOf(classOf[SampleUntypedConsumer])
val event = ConsumerActorRegistered.eventFor(uc)
assert(event === Some(ConsumerActorRegistered(uc, consumerOf(uc))))
}
@Test
def shouldCreateSomeBlockingPublishRequestFromUntypedConsumer = {
val uc = Actor.actorOf(classOf[SampleUntypedConsumerBlocking])
val event = ConsumerActorRegistered.eventFor(uc)
assert(event === Some(ConsumerActorRegistered(uc, consumerOf(uc))))
}
@Test
def shouldCreateNoneFromUntypedConsumer = {
val a = Actor.actorOf(classOf[SampleUntypedActor])
val event = ConsumerActorRegistered.eventFor(a)
assert(event === None)
}
private def consumerOf(ref: ActorRef) = ref match {
case l: LocalActorRef l.underlyingActorInstance.asInstanceOf[Consumer]
case _ null: Consumer
}
}
object ConsumerRegisteredTest {
class ConsumerActor1 extends Actor with Consumer {
def endpointUri = "mock:test1"
protected def receive = null
}
class ConsumerActor2 extends Actor with Consumer {
def endpointUri = "mock:test2"
override def blocking = true
protected def receive = null
}
class PlainActor extends Actor {
protected def receive = null
}
}

View file

@ -1,295 +0,0 @@
package akka.camel
import java.util.concurrent.{ TimeoutException, CountDownLatch, TimeUnit }
import org.apache.camel.{ AsyncProcessor, AsyncCallback, CamelExecutionException }
import org.apache.camel.builder.Builder
import org.apache.camel.component.direct.DirectEndpoint
import org.apache.camel.model.RouteDefinition
import org.scalatest.{ BeforeAndAfterAll, WordSpec }
import org.scalatest.matchers.MustMatchers
import akka.actor.Actor._
import akka.actor._
/**
* @author Martin Krasser
*/
class ConsumerScalaTest extends WordSpec with BeforeAndAfterAll with MustMatchers {
import CamelContextManager.mandatoryContext
import CamelContextManager.mandatoryTemplate
import ConsumerScalaTest._
var service: CamelService = null
override protected def beforeAll = {
registry.local.shutdownAll
service = CamelServiceFactory.createCamelService
// register test consumer before registering the publish requestor
// and before starting the CamelService (registry is scanned for consumers)
actorOf(Props(new TestConsumer("direct:publish-test-1"))
service.registerPublishRequestor
service.awaitEndpointActivation(1) {
service.start
} must be(true)
}
override protected def afterAll = {
service.stop
registry.local.shutdownAll
}
"A responding consumer" when {
var consumer: ActorRef = null
"started before starting the CamelService" must {
"support an in-out message exchange via its endpoint" in {
mandatoryTemplate.requestBody("direct:publish-test-1", "msg1") must equal("received msg1")
}
}
"not started" must {
"not have an associated endpoint in the CamelContext" in {
CamelContextManager.mandatoryContext.hasEndpoint("direct:publish-test-2") must be(null)
}
}
"started" must {
"support an in-out message exchange via its endpoint" in {
service.awaitEndpointActivation(1) {
consumer = actorOf(Props(new TestConsumer("direct:publish-test-2"))
} must be(true)
mandatoryTemplate.requestBody("direct:publish-test-2", "msg2") must equal("received msg2")
}
"have an associated endpoint in the CamelContext" in {
CamelContextManager.mandatoryContext.hasEndpoint("direct:publish-test-2") must not be (null)
}
}
"stopped" must {
"not support an in-out message exchange via its endpoint" in {
service.awaitEndpointDeactivation(1) {
consumer.stop
} must be(true)
intercept[CamelExecutionException] {
mandatoryTemplate.requestBody("direct:publish-test-2", "msg2")
}
}
}
}
"A responding, untyped consumer" when {
var consumer: ActorRef = null
"started" must {
"support an in-out message exchange via its endpoint" in {
service.awaitEndpointActivation(1) {
consumer = Actor.actorOf(classOf[SampleUntypedConsumer])
} must be(true)
mandatoryTemplate.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 {
service.awaitEndpointDeactivation(1) {
consumer.stop
} must be(true)
intercept[CamelExecutionException] {
mandatoryTemplate.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 {
service.awaitEndpointActivation(1) {
actorOf(Props(creator = () new TestBlocker("direct:publish-test-5")))
} must be(true)
try {
mandatoryTemplate.requestBody("direct:publish-test-5", "msg3")
fail("expected TimoutException not thrown")
} catch {
case e {
assert(e.getCause.isInstanceOf[TimeoutException])
}
}
}
}
}
"A responding, blocking consumer" when {
"activated with a custom error handler" must {
"handle thrown exceptions by generating a custom response" in {
service.awaitEndpointActivation(1) {
actorOf(Props[ErrorHandlingConsumer]
} must be(true)
mandatoryTemplate.requestBody("direct:error-handler-test", "hello") must equal("error: hello")
}
}
"activated with a custom redelivery handler" must {
"handle thrown exceptions by redelivering the initial message" in {
service.awaitEndpointActivation(1) {
actorOf(Props[RedeliveringConsumer]
} must be(true)
mandatoryTemplate.requestBody("direct:redelivery-test", "hello") must equal("accepted: hello")
}
}
}
"An non auto-acknowledging consumer" when {
"started" must {
"must support acknowledgements on system level" in {
var consumer: ActorRef = null
service.awaitEndpointActivation(1) {
consumer = actorOf(Props(new TestAckConsumer("direct:system-ack-test"))
} must be(true)
val endpoint = mandatoryContext.getEndpoint("direct:system-ack-test", classOf[DirectEndpoint])
val producer = endpoint.createProducer.asInstanceOf[AsyncProcessor]
val exchange = endpoint.createExchange
val latch = new CountDownLatch(1)
val handler = new AsyncCallback {
def done(doneSync: Boolean) = {
doneSync must be(false)
latch.countDown
}
}
exchange.getIn.setBody("test")
producer.process(exchange, handler)
latch.await(5, TimeUnit.SECONDS) must be(true)
consumer.stop
}
}
}
"A supervised consumer" must {
"be able to reply during receive" in {
val consumer = Actor.actorOf(Props(new SupervisedConsumer("reply-channel-test-1"))
(consumer ? "succeed").get must equal("ok")
}
"be able to reply on failure during preRestart" in {
val consumer = Actor.actorOf(Props(new SupervisedConsumer("reply-channel-test-2"))
val supervisor = Supervisor(
SupervisorConfig(
OneForOneStrategy(List(classOf[Exception]), 2, 10000),
Supervise(consumer, Permanent) :: Nil))
val latch = new CountDownLatch(1)
val sender = Actor.actorOf(Props(new Sender("pr", latch))
consumer.!("fail")(Some(sender))
latch.await(5, TimeUnit.SECONDS) must be(true)
}
"be able to reply on failure during postStop" in {
val consumer = Actor.actorOf(Props(new SupervisedConsumer("reply-channel-test-3")))
val supervisor = Supervisor(
SupervisorConfig(
OneForOneStrategy(List(classOf[Exception]), Some(0)),
Supervise(consumer, Temporary) :: Nil))
val latch = new CountDownLatch(1)
val sender = Actor.actorOf(Props(new Sender("ps", latch))
consumer.!("fail")(Some(sender))
latch.await(5, TimeUnit.SECONDS) must be(true)
}
}
}
object ConsumerScalaTest {
trait BlockingConsumer extends Consumer { self: Actor
override def blocking = true
}
class TestConsumer(uri: String) extends Actor with Consumer {
def endpointUri = uri
protected def receive = {
case msg: Message sender ! "received %s" format msg.body
}
}
class TestBlocker(uri: String) extends Actor with BlockingConsumer {
def endpointUri = uri
protected def receive = {
case msg: Message { /* do not reply */ }
}
}
class TestAckConsumer(uri: String) extends Actor with Consumer {
def endpointUri = uri
override def autoack = false
protected def receive = {
case msg: Message sender ! Ack
}
}
class ErrorHandlingConsumer extends Actor with BlockingConsumer {
def endpointUri = "direct:error-handler-test"
onRouteDefinition { rd: RouteDefinition
rd.onException(classOf[Exception]).handled(true).transform(Builder.exceptionMessage).end
}
protected def receive = {
case msg: Message throw new Exception("error: %s" format msg.body)
}
}
class SupervisedConsumer(name: String) extends Actor with Consumer {
def endpointUri = "direct:%s" format name
protected def receive = {
case "fail" { throw new Exception("test") }
case "succeed" sender ! "ok"
}
override def preRestart(reason: scala.Throwable, msg: Option[Any]) {
sender.tell("pr")
}
override def postStop {
sender.tell("ps")
}
}
class Sender(expected: String, latch: CountDownLatch) extends Actor {
def receive = {
case msg if (msg == expected) latch.countDown
case _ {}
}
}
class RedeliveringConsumer extends Actor with BlockingConsumer {
def endpointUri = "direct:redelivery-test"
onRouteDefinition { rd: RouteDefinition
rd.onException(classOf[Exception]).maximumRedeliveries(1).end
}
//
// first message to this actor is not valid and will be rejected
//
var valid = false
protected def receive = {
case msg: Message try {
respondTo(msg)
} finally {
valid = true
}
}
private def respondTo(msg: Message) =
if (valid) sender ! ("accepted: %s" format msg.body)
else throw new Exception("rejected: %s" format msg.body)
}
}

View file

@ -0,0 +1,59 @@
/**
* Copyright (C) 2009when2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camel
import akka.camel.TestSupport.SharedCamelSystem
import internal.DefaultCamel
import org.scalatest.matchers.MustMatchers
import org.scalatest.mock.MockitoSugar
import akka.actor.ActorSystem
import org.apache.camel.{ CamelContext, ProducerTemplate }
import org.scalatest.WordSpec
import akka.event.LoggingAdapter
class DefaultCamelTest extends WordSpec with SharedCamelSystem with MustMatchers with MockitoSugar {
import org.mockito.Mockito.{ when, verify }
def camelWitMocks = new DefaultCamel(mock[ActorSystem]) {
override val log = mock[LoggingAdapter]
override lazy val template = mock[ProducerTemplate]
override lazy val context = mock[CamelContext]
}
"during shutdown, when both context and template fail to shutdown" when {
val camel = camelWitMocks
when(camel.context.stop()) thenThrow new RuntimeException("context")
when(camel.template.stop()) thenThrow new RuntimeException("template")
val exception = intercept[RuntimeException] {
camel.shutdown()
}
"throws exception thrown by context.stop()" in {
exception.getMessage() must be("context");
}
"tries to stop both template and context" in {
verify(camel.template).stop()
verify(camel.context).stop()
}
}
"during start, if template fails to start, it will stop the context" in {
val camel = camelWitMocks
when(camel.template.start()) thenThrow new RuntimeException
intercept[RuntimeException] {
camel.start
}
verify(camel.context).stop()
}
}

View file

@ -1,3 +1,7 @@
/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camel
import org.scalatest.junit.JUnitSuite

View file

@ -1,94 +1,78 @@
/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camel
import java.io.InputStream
import org.apache.camel.NoTypeConversionAvailableException
import org.junit.Assert._
import org.junit.Test
import akka.camel.TestSupport.{ SharedCamelSystem, MessageSugar }
import org.scalatest.FunSuite
import org.scalatest.matchers.MustMatchers
import org.scalatest.BeforeAndAfterAll
import org.scalatest.junit.JUnitSuite
class MessageScalaTest extends JUnitSuite with BeforeAndAfterAll {
override protected def beforeAll = CamelContextManager.init
@Test
def shouldConvertDoubleBodyToString = {
assertEquals("1.4", Message(1.4).bodyAs[String])
class MessageScalaTest extends FunSuite with MustMatchers with SharedCamelSystem with MessageSugar {
implicit def camelContext = camel.context
test("mustConvertDoubleBodyToString") {
Message(1.4).bodyAs[String] must be("1.4")
}
@Test
def shouldThrowExceptionWhenConvertingDoubleBodyToInputStream {
test("mustThrowExceptionWhenConvertingDoubleBodyToInputStream") {
intercept[NoTypeConversionAvailableException] {
Message(1.4).bodyAs[InputStream]
}
}
@Test
def shouldReturnDoubleHeader = {
test("mustReturnDoubleHeader") {
val message = Message("test", Map("test" -> 1.4))
assertEquals(1.4, message.header("test"))
message.header("test").get must be(1.4)
}
@Test
def shouldConvertDoubleHeaderToString = {
test("mustConvertDoubleHeaderToString") {
val message = Message("test", Map("test" -> 1.4))
assertEquals("1.4", message.headerAs[String]("test"))
message.headerAs[String]("test").get must be("1.4")
}
@Test
def shouldReturnSubsetOfHeaders = {
test("mustReturnSubsetOfHeaders") {
val message = Message("test", Map("A" -> "1", "B" -> "2"))
assertEquals(Map("B" -> "2"), message.headers(Set("B")))
message.headers(Set("B")) must be(Map("B" -> "2"))
}
@Test
def shouldTransformBodyAndPreserveHeaders = {
assertEquals(
Message("ab", Map("A" -> "1")),
Message("a", Map("A" -> "1")).transformBody((body: String) body + "b"))
test("mustTransformBodyAndPreserveHeaders") {
Message("a", Map("A" -> "1")).mapBody((body: String) body + "b") must be(Message("ab", Map("A" -> "1")))
}
@Test
def shouldConvertBodyAndPreserveHeaders = {
assertEquals(
Message("1.4", Map("A" -> "1")),
Message(1.4, Map("A" -> "1")).setBodyAs[String])
test("mustConvertBodyAndPreserveHeaders") {
Message(1.4, Map("A" -> "1")).withBodyAs[String] must be(Message("1.4", Map("A" -> "1")))
}
@Test
def shouldSetBodyAndPreserveHeaders = {
assertEquals(
Message("test2", Map("A" -> "1")),
Message("test1", Map("A" -> "1")).setBody("test2"))
}
@Test
def shouldSetHeadersAndPreserveBody = {
assertEquals(
Message("test1", Map("C" -> "3")),
Message("test1", Map("A" -> "1")).setHeaders(Map("C" -> "3")))
test("mustSetBodyAndPreserveHeaders") {
Message("test1", Map("A" -> "1")).withBody("test2") must be(
Message("test2", Map("A" -> "1")))
}
@Test
def shouldAddHeaderAndPreserveBodyAndHeaders = {
assertEquals(
Message("test1", Map("A" -> "1", "B" -> "2")),
Message("test1", Map("A" -> "1")).addHeader("B" -> "2"))
test("mustSetHeadersAndPreserveBody") {
Message("test1", Map("A" -> "1")).withHeaders(Map("C" -> "3")) must be(
Message("test1", Map("C" -> "3")))
}
@Test
def shouldAddHeadersAndPreserveBodyAndHeaders = {
assertEquals(
Message("test1", Map("A" -> "1", "B" -> "2")),
Message("test1", Map("A" -> "1")).addHeaders(Map("B" -> "2")))
test("mustAddHeaderAndPreserveBodyAndHeaders") {
Message("test1", Map("A" -> "1")).addHeader("B" -> "2") must be(
Message("test1", Map("A" -> "1", "B" -> "2")))
}
@Test
def shouldRemoveHeadersAndPreserveBodyAndRemainingHeaders = {
assertEquals(
Message("test1", Map("A" -> "1")),
Message("test1", Map("A" -> "1", "B" -> "2")).removeHeader("B"))
test("mustAddHeadersAndPreserveBodyAndHeaders") {
Message("test1", Map("A" -> "1")).addHeaders(Map("B" -> "2")) must be(
Message("test1", Map("A" -> "1", "B" -> "2")))
}
test("mustRemoveHeadersAndPreserveBodyAndRemainingHeaders") {
Message("test1", Map("A" -> "1", "B" -> "2")).withoutHeader("B") must be(
Message("test1", Map("A" -> "1")))
}
}

View file

@ -1,250 +1,282 @@
/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package 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 akka.actor._
import akka.pattern._
import akka.dispatch.Await
import akka.util.duration._
import akka.camel.TestSupport.SharedCamelSystem
import org.scalatest._
import akka.actor.Actor._
import akka.actor.{ ActorRef, Actor }
/**
* Tests the features of the Camel Producer.
*/
class ProducerFeatureTest extends WordSpec with BeforeAndAfterAll with BeforeAndAfterEach with SharedCamelSystem with GivenWhenThen {
class ProducerFeatureTest extends FeatureSpec with BeforeAndAfterAll with BeforeAndAfterEach with GivenWhenThen {
import ProducerFeatureTest._
override protected def beforeAll = {
Actor.registry.local.shutdownAll
CamelContextManager.init
CamelContextManager.mandatoryContext.addRoutes(new TestRoute)
CamelContextManager.start
}
val camelContext = camel.context
// to make testing equality of messages easier, otherwise the breadcrumb shows up in the result.
camelContext.setUseBreadcrumb(false)
override protected def afterAll = {
CamelContextManager.stop
Actor.registry.local.shutdownAll
}
val timeout = 1 second
override protected def afterEach = {
mockEndpoint.reset
}
override protected def beforeAll { camelContext.addRoutes(new TestRoute(system)) }
feature("Produce a message to a sync Camel route") {
override protected def afterEach { mockEndpoint.reset() }
scenario("produce message and receive normal response") {
"A Producer on a sync Camel route" must {
"produce a message and receive normal response" in {
given("a registered two-way producer")
val producer = actorOf(Props(new TestProducer("direct:producer-test-2", true))
val producer = system.actorOf(Props(new TestProducer("direct:producer-test-2", true)))
when("a test message is sent to the producer with ?")
val message = Message("test", Map(Message.MessageExchangeId -> "123"))
val result = (producer ? message).get
then("a normal response should have been returned by the producer")
val expected = Message("received TEST", Map(Message.MessageExchangeId -> "123"))
assert(result === expected)
val message = CamelMessage("test", Map(CamelMessage.MessageExchangeId -> "123"))
val future = producer.ask(message)(timeout)
then("a normal response must have been returned by the producer")
val expected = CamelMessage("received TEST", Map(CamelMessage.MessageExchangeId -> "123"))
Await.result(future, timeout) match {
case result: CamelMessage assert(result === expected)
case unexpected fail("Actor responded with unexpected message:" + unexpected)
}
}
scenario("produce message and receive failure response") {
"produce a message and receive failure response" in {
given("a registered two-way producer")
val producer = actorOf(Props(new TestProducer("direct:producer-test-2"))
val producer = system.actorOf(Props(new TestProducer("direct:producer-test-2")))
when("a test message causing an exception is sent to the producer with ?")
val message = Message("fail", Map(Message.MessageExchangeId -> "123"))
val result = (producer ? message).as[Failure]
then("a failure response should have been returned by the producer")
val expectedFailureText = result.get.cause.getMessage
val expectedHeaders = result.get.headers
assert(expectedFailureText === "failure")
assert(expectedHeaders === Map(Message.MessageExchangeId -> "123"))
}
scenario("produce message oneway") {
given("a registered one-way producer")
val producer = actorOf(Props(new TestProducer("direct:producer-test-1", true) with Oneway)
when("a test message is sent to the producer with !")
mockEndpoint.expectedBodiesReceived("TEST")
producer ! Message("test")
then("the test message should have been sent to mock:mock")
mockEndpoint.assertIsSatisfied
}
scenario("produce message twoway without sender reference") {
given("a registered two-way producer")
val producer = actorOf(Props(new TestProducer("direct:producer-test-1"))
when("a test message is sent to the producer with !")
mockEndpoint.expectedBodiesReceived("test")
producer ! Message("test")
then("there should be only a warning that there's no sender reference")
mockEndpoint.assertIsSatisfied
}
}
feature("Produce a message to an async Camel route") {
scenario("produce message and receive normal response") {
given("a registered two-way producer")
val producer = actorOf(Props(new TestProducer("direct:producer-test-3"))
when("a test message is sent to the producer with ?")
val message = Message("test", Map(Message.MessageExchangeId -> "123"))
val result = (producer ? message).as[Message].get
then("a normal response should have been returned by the producer")
assert(result.headers(Message.MessageExchangeId) === "123")
}
scenario("produce message and receive failure response") {
given("a registered two-way producer")
val producer = actorOf(Props(new TestProducer("direct:producer-test-3"))
when("a test message causing an exception is sent to the producer with ?")
val message = Message("fail", Map(Message.MessageExchangeId -> "123"))
val result = (producer ? message).as[Failure]
then("a failure response should have been returned by the producer")
val expectedFailureText = result.get.cause.getMessage
val expectedHeaders = result.get.headers
assert(expectedFailureText === "failure")
assert(expectedHeaders === Map(Message.MessageExchangeId -> "123"))
}
}
feature("Produce a message to a sync Camel route and then forward the 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(Props[ReplyingForwardTarget]
val producer = actorOf(Props(new TestForwarder("direct:producer-test-2", target))
when("a test message is sent to the producer with ?")
val message = Message("test", Map(Message.MessageExchangeId -> "123"))
val result = (producer ? message).get
then("a normal response should have been returned by the forward target")
val expected = Message("received test", Map(Message.MessageExchangeId -> "123", "test" -> "result"))
assert(result === expected)
}
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(Props[ReplyingForwardTarget]
val producer = actorOf(Props(new TestForwarder("direct:producer-test-2", target))
when("a test message causing an exception is sent to the producer with ?")
val message = Message("fail", Map(Message.MessageExchangeId -> "123"))
val result = (producer ? message).as[Failure].get
then("a failure response should have been returned by the forward target")
val message = CamelMessage("fail", Map(CamelMessage.MessageExchangeId -> "123"))
val future = producer.ask(message)(timeout)
Await.result(future, timeout) match {
case result: Failure
then("a failure response must have been returned by the producer")
val expectedFailureText = result.cause.getMessage
val expectedHeaders = result.headers
assert(expectedFailureText === "failure")
assert(expectedHeaders === Map(Message.MessageExchangeId -> "123", "test" -> "failure"))
assert(expectedHeaders === Map(CamelMessage.MessageExchangeId -> "123"))
case unexpected fail("Actor responded with unexpected message:" + unexpected)
}
}
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(Props[ProducingForwardTarget]
val producer = actorOf(Props(new TestForwarder("direct:producer-test-2", target))
"produce a message oneway" in {
given("a registered one-way producer")
val producer = system.actorOf(Props(new TestProducer("direct:producer-test-1", true) with Oneway))
when("a test message is sent to the producer with !")
mockEndpoint.expectedBodiesReceived("received test")
val result = producer.!(Message("test"))(Some(producer))
mockEndpoint.expectedBodiesReceived("TEST")
producer ! CamelMessage("test", Map())
then("a normal response should have been produced by the forward target")
mockEndpoint.assertIsSatisfied
then("the test message must have been sent to mock:mock")
mockEndpoint.assertIsSatisfied()
}
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(Props[ProducingForwardTarget]
val producer = actorOf(Props(new TestForwarder("direct:producer-test-2", target))
"produces message twoway without sender reference" in {
given("a registered two-way producer")
val producer = system.actorOf(Props(new TestProducer("direct:producer-test-1")))
when("a test message causing an exception is sent to the producer with !")
mockEndpoint.expectedMessageCount(1)
mockEndpoint.message(0).body().isInstanceOf(classOf[Failure])
val result = producer.!(Message("fail"))(Some(producer))
when("a test message is sent to the producer with !")
mockEndpoint.expectedBodiesReceived("test")
producer ! CamelMessage("test", Map())
then("a failure response should have been produced by the forward target")
mockEndpoint.assertIsSatisfied
then("there must be only a warning that there's no sender reference")
mockEndpoint.assertIsSatisfied()
}
}
feature("Produce a message to an async Camel route and then forward the response") {
"A Producer on an async Camel route" must {
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(Props[ReplyingForwardTarget]
val producer = actorOf(Props(new TestForwarder("direct:producer-test-3", target))
"produce message to direct:producer-test-3 and receive normal response" in {
given("a registered two-way producer")
val producer = system.actorOf(Props(new TestProducer("direct:producer-test-3")))
when("a test message is sent to the producer with ?")
val message = Message("test", Map(Message.MessageExchangeId -> "123"))
val result = (producer ? message).as[Message].get
val message = CamelMessage("test", Map(CamelMessage.MessageExchangeId -> "123"))
val future = producer.ask(message)(timeout)
then("a normal response should have been returned by the forward target")
assert(result.headers(Message.MessageExchangeId) === "123")
assert(result.headers("test") === "result")
Await.result(future, timeout) match {
case result: CamelMessage
then("a normal response must have been returned by the producer")
val expected = CamelMessage("received test", Map(CamelMessage.MessageExchangeId -> "123"))
assert(result === expected)
case unexpected fail("Actor responded with unexpected message:" + unexpected)
}
}
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(Props[ReplyingForwardTarget]
val producer = actorOf(Props(new TestForwarder("direct:producer-test-3", target))
"produce message to direct:producer-test-3 and receive failure response" in {
given("a registered two-way producer")
val producer = system.actorOf(Props(new TestProducer("direct:producer-test-3")))
when("a test message causing an exception is sent to the producer with ?")
val message = Message("fail", Map(Message.MessageExchangeId -> "123"))
val result = (producer ? message).as[Failure]
then("a failure response should have been returned by the forward target")
val expectedFailureText = result.get.cause.getMessage
val expectedHeaders = result.get.headers
val message = CamelMessage("fail", Map(CamelMessage.MessageExchangeId -> "123"))
val future = producer.ask(message)(timeout)
Await.result(future, timeout) match {
case result: Failure
then("a failure response must have been returned by the producer")
val expectedFailureText = result.cause.getMessage
val expectedHeaders = result.headers
assert(expectedFailureText === "failure")
assert(expectedHeaders === Map(Message.MessageExchangeId -> "123", "test" -> "failure"))
assert(expectedHeaders === Map(CamelMessage.MessageExchangeId -> "123"))
case unexpected fail("Actor responded with unexpected message:" + unexpected)
}
}
scenario("produce message, forward normal response to a producing target actor and produce response to direct:forward-test-1") {
"produce message, forward normal response of direct:producer-test-2 to a replying target actor and receive response" in {
given("a registered two-way producer configured with a forward target")
val target = system.actorOf(Props[ReplyingForwardTarget])
val producer = system.actorOf(Props(new TestForwarder("direct:producer-test-2", target)))
when("a test message is sent to the producer with ?")
val message = CamelMessage("test", Map(CamelMessage.MessageExchangeId -> "123"))
val future = producer.ask(message)(timeout)
Await.result(future, timeout) match {
case result: CamelMessage
then("a normal response must have been returned by the forward target")
val expected = CamelMessage("received test", Map(CamelMessage.MessageExchangeId -> "123", "test" -> "result"))
assert(result === expected)
case unexpected fail("Actor responded with unexpected message:" + unexpected)
}
}
"produce message, forward failure response of direct:producer-test-2 to a replying target actor and receive response" in {
given("a registered two-way producer configured with a forward target")
val target = system.actorOf(Props[ReplyingForwardTarget])
val producer = system.actorOf(Props(new TestForwarder("direct:producer-test-2", target)))
when("a test message causing an exception is sent to the producer with ?")
val message = CamelMessage("fail", Map(CamelMessage.MessageExchangeId -> "123"))
val future = producer.ask(message)(timeout)
Await.result(future, timeout) match {
case failure: Failure
then("a failure response must have been returned by the forward target")
val expectedFailureText = failure.cause.getMessage
val expectedHeaders = failure.headers
assert(expectedFailureText === "failure")
assert(expectedHeaders === Map(CamelMessage.MessageExchangeId -> "123", "test" -> "failure"))
case unexpected fail("Actor responded with unexpected message:" + unexpected)
}
}
"produce message, forward normal response to a producing target actor and produce response to direct:forward-test-1" in {
given("a registered one-way producer configured with a forward target")
val target = actorOf(Props[ProducingForwardTarget]
val producer = actorOf(Props(new TestForwarder("direct:producer-test-3", target))
val target = system.actorOf(Props[ProducingForwardTarget])
val producer = system.actorOf(Props(new TestForwarder("direct:producer-test-2", target)))
when("a test message is sent to the producer with !")
mockEndpoint.expectedBodiesReceived("received test")
val result = producer.!(Message("test"))(Some(producer))
producer.tell(CamelMessage("test", Map()), producer)
then("a normal response should have been produced by the forward target")
mockEndpoint.assertIsSatisfied
then("a normal response must have been produced by the forward target")
mockEndpoint.assertIsSatisfied()
}
scenario("produce message, forward failure response to a producing target actor and produce response to direct:forward-test-1") {
"produce message, forward failure response to a producing target actor and produce response to direct:forward-test-1" in {
given("a registered one-way producer configured with a forward target")
val target = actorOf(Props[ProducingForwardTarget]
val producer = actorOf(Props(new TestForwarder("direct:producer-test-3", target))
val target = system.actorOf(Props[ProducingForwardTarget])
val producer = system.actorOf(Props(new TestForwarder("direct:producer-test-2", target)))
when("a test message causing an exception is sent to the producer with !")
mockEndpoint.expectedMessageCount(1)
mockEndpoint.message(0).body().isInstanceOf(classOf[Failure])
val result = producer.!(Message("fail"))(Some(producer))
producer.tell(CamelMessage("fail", Map()), producer)
then("a failure response should have been produced by the forward target")
mockEndpoint.assertIsSatisfied
then("a failure response must have been produced by the forward target")
mockEndpoint.assertIsSatisfied()
}
"produce message, forward normal response from direct:producer-test-3 to a replying target actor and receive response" in {
given("a registered two-way producer configured with a forward target")
val target = system.actorOf(Props[ReplyingForwardTarget])
val producer = system.actorOf(Props(new TestForwarder("direct:producer-test-3", target)))
when("a test message is sent to the producer with ?")
val message = CamelMessage("test", Map(CamelMessage.MessageExchangeId -> "123"))
val future = producer.ask(message)(timeout)
then("a normal response must have been returned by the forward target")
Await.result(future, timeout) match {
case message: CamelMessage
val expected = CamelMessage("received test", Map(CamelMessage.MessageExchangeId -> "123", "test" -> "result"))
assert(message === expected)
case unexpected fail("Actor responded with unexpected message:" + unexpected)
}
}
private def mockEndpoint = CamelContextManager.mandatoryContext.getEndpoint("mock:mock", classOf[MockEndpoint])
"produce message, forward failure response from direct:producer-test-3 to a replying target actor and receive response" in {
given("a registered two-way producer configured with a forward target")
val target = system.actorOf(Props[ReplyingForwardTarget])
val producer = system.actorOf(Props(new TestForwarder("direct:producer-test-3", target)))
when("a test message causing an exception is sent to the producer with !!")
val message = CamelMessage("fail", Map(CamelMessage.MessageExchangeId -> "123"))
val future = producer.ask(message)(timeout)
Await.result(future, timeout) match {
case failure: Failure
then("a failure response must have been returned by the forward target")
val expectedFailureText = failure.cause.getMessage
val expectedHeaders = failure.headers
assert(expectedFailureText === "failure")
assert(expectedHeaders === Map(CamelMessage.MessageExchangeId -> "123", "test" -> "failure"))
case unexpected fail("Actor responded with unexpected message:" + unexpected)
}
}
"produce message, forward normal response from direct:producer-test-3 to a producing target actor and produce response to direct:forward-test-1" in {
given("a registered one-way producer configured with a forward target")
val target = system.actorOf(Props[ProducingForwardTarget])
val producer = system.actorOf(Props(new TestForwarder("direct:producer-test-3", target)))
when("a test message is sent to the producer with !")
mockEndpoint.expectedBodiesReceived("received test")
producer.tell(CamelMessage("test", Map()), producer)
then("a normal response must have been produced by the forward target")
mockEndpoint.assertIsSatisfied()
}
"produce message, forward failure response from direct:producer-test-3 to a producing target actor and produce response to direct:forward-test-1" in {
given("a registered one-way producer configured with a forward target")
val target = system.actorOf(Props[ProducingForwardTarget])
val producer = system.actorOf(Props(new TestForwarder("direct:producer-test-3", target)))
when("a test message causing an exception is sent to the producer with !")
mockEndpoint.expectedMessageCount(1)
mockEndpoint.message(0).body().isInstanceOf(classOf[Failure])
producer.tell(CamelMessage("fail", Map()), producer)
then("a failure response must have been produced by the forward target")
mockEndpoint.assertIsSatisfied()
}
}
private def mockEndpoint = camel.context.getEndpoint("mock:mock", classOf[MockEndpoint])
}
object ProducerFeatureTest {
class TestProducer(uri: String, upper: Boolean = false) extends Actor with Producer {
def endpointUri = uri
override protected def receiveBeforeProduce = {
case msg: Message if (upper) msg.transformBody { body: String body.toUpperCase } else msg
case msg: CamelMessage if (upper) msg.mapBody {
body: String body.toUpperCase
}
else msg
}
}
class TestForwarder(uri: String, target: ActorRef) extends Actor with Producer {
def endpointUri = uri
override protected def receiveAfterProduce = {
case msg target forward msg
}
@ -252,17 +284,22 @@ object ProducerFeatureTest {
class TestResponder extends Actor {
protected def receive = {
case msg: Message msg.body match {
case "fail" sender ! Failure(new Exception("failure"), msg.headers)
case _ sender ! (msg.transformBody { body: String "received %s" format body })
case msg: CamelMessage msg.body match {
case "fail" context.sender ! (Failure(new Exception("failure"), msg.headers))
case _
context.sender ! (msg.mapBody {
body: String "received %s" format body
})
}
}
}
class ReplyingForwardTarget extends Actor {
protected def receive = {
case msg: Message sender ! msg.addHeader("test" -> "result")
case msg: Failure sender ! Failure(msg.cause, msg.headers + ("test" -> "failure"))
case msg: CamelMessage
context.sender ! (msg.addHeader("test" -> "result"))
case msg: Failure
context.sender ! (Failure(msg.cause, msg.headers + ("test" -> "failure")))
}
}
@ -270,14 +307,15 @@ object ProducerFeatureTest {
def endpointUri = "direct:forward-test-1"
}
class TestRoute extends RouteBuilder {
val responder = actorOf(Props[TestResponder]
class TestRoute(system: ActorSystem) extends RouteBuilder {
val responder = system.actorOf(Props[TestResponder], name = "TestResponder")
def configure {
from("direct:forward-test-1").to("mock:mock")
// for one-way messaging tests
from("direct:producer-test-1").to("mock:mock")
// for two-way messaging tests (async)
from("direct:producer-test-3").to("actor:uuid:%s" format responder.uuid)
from("direct:producer-test-3").to(responder)
// for two-way messaging tests (sync)
from("direct:producer-test-2").process(new Processor() {
def process(exchange: Exchange) = {
@ -289,4 +327,5 @@ object ProducerFeatureTest {
})
}
}
}

View file

@ -0,0 +1,47 @@
/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camel
import org.scalatest.matchers.MustMatchers
import org.scalatest.WordSpec
import akka.camel.TestSupport.SharedCamelSystem
import akka.util.duration._
import akka.actor.{ ActorRef, Props }
class ProducerRegistryTest extends WordSpec with MustMatchers with SharedCamelSystem {
"A ProducerRegistry" must {
def newEmptyActor: ActorRef = system.actorOf(Props.empty)
def registerProcessorFor(actorRef: ActorRef) = camel.registerProducer(actorRef, "mock:mock")._2
"register a started SendProcessor for the producer, which is stopped when the actor is stopped" in {
val actorRef = newEmptyActor
val processor = registerProcessorFor(actorRef)
camel.awaitActivation(actorRef, 1 second)
processor.isStarted must be(true)
system.stop(actorRef)
camel.awaitDeactivation(actorRef, 1 second)
(processor.isStopping || processor.isStopped) must be(true)
}
"remove and stop the SendProcessor if the actorRef is registered" in {
val actorRef = newEmptyActor
val processor = registerProcessorFor(actorRef)
camel.unregisterProducer(actorRef)
(processor.isStopping || processor.isStopped) must be(true)
}
"remove and stop only the SendProcessor for the actorRef that is registered" in {
val actorRef1 = newEmptyActor
val actorRef2 = newEmptyActor
val processor = registerProcessorFor(actorRef2)
// this generates a warning on the activationTracker.
camel.unregisterProducer(actorRef1)
(!processor.isStopped && !processor.isStopping) must be(true)
camel.unregisterProducer(actorRef2)
(processor.isStopping || processor.isStopped) must be(true)
}
}
}

View file

@ -0,0 +1,90 @@
/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camel
import akka.actor.{ Props, ActorSystem, Actor }
import akka.util.duration._
import java.util.concurrent.{ TimeoutException, ExecutionException, TimeUnit }
import org.scalatest.{ BeforeAndAfterEach, BeforeAndAfterAll, Suite }
import org.scalatest.matchers.{ BePropertyMatcher, BePropertyMatchResult }
import akka.util.{ FiniteDuration, Duration }
private[camel] object TestSupport {
def start(actor: Actor)(implicit system: ActorSystem) = {
val actorRef = system.actorOf(Props(actor))
CamelExtension(system).awaitActivation(actorRef, 1 second)
actorRef
}
private[camel] implicit def camelToTestWrapper(camel: Camel) = new CamelTestWrapper(camel)
class CamelTestWrapper(camel: Camel) {
/**
* Sends msg to the endpoint and returns response.
* It only waits for the response until timeout passes.
* This is to reduce cases when unit-tests block infinitely.
*/
def sendTo(to: String, msg: String, timeout: Duration = 1 second): AnyRef = {
try {
camel.template.asyncRequestBody(to, msg).get(timeout.toNanos, TimeUnit.NANOSECONDS)
} catch {
case e: ExecutionException throw e.getCause
case e: TimeoutException throw new AssertionError("Failed to get response to message [%s], send to endpoint [%s], within [%s]" format (msg, to, timeout), e)
}
}
def routeCount = camel.context.getRoutes().size()
}
@deprecated
trait MessageSugar {
def Message(body: Any) = akka.camel.CamelMessage(body, Map.empty)
def Message(body: Any, headers: Map[String, Any]) = akka.camel.CamelMessage(body, headers)
}
trait SharedCamelSystem extends BeforeAndAfterAll { this: Suite
implicit lazy val system = ActorSystem("test")
implicit lazy val camel = CamelExtension(system)
abstract override protected def afterAll() {
super.afterAll()
system.shutdown()
}
}
trait NonSharedCamelSystem extends BeforeAndAfterEach { this: Suite
implicit var system: ActorSystem = _
implicit var camel: Camel = _
override protected def beforeEach() {
super.beforeEach()
system = ActorSystem("test")
camel = CamelExtension(system)
}
override protected def afterEach() {
system.shutdown()
super.afterEach()
}
}
def time[A](block: A): FiniteDuration = {
val start = System.currentTimeMillis()
block
val duration = System.currentTimeMillis() - start
duration millis
}
def anInstanceOf[T](implicit manifest: Manifest[T]) = {
val clazz = manifest.erasure.asInstanceOf[Class[T]]
new BePropertyMatcher[AnyRef] {
def apply(left: AnyRef) = BePropertyMatchResult(
clazz.isAssignableFrom(left.getClass),
"an instance of " + clazz.getName)
}
}
}

View file

@ -1,94 +0,0 @@
package 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 akka.actor.Actor._
class UntypedProducerFeatureTest extends FeatureSpec with BeforeAndAfterAll with BeforeAndAfterEach with GivenWhenThen {
import UntypedProducerFeatureTest._
override protected def beforeAll = {
registry.local.shutdownAll
CamelContextManager.init
CamelContextManager.mandatoryContext.addRoutes(new TestRoute)
CamelContextManager.start
}
override protected def afterAll = {
CamelContextManager.stop
registry.local.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])
when("a test message is sent to the producer with ?")
val message = Message("test", Map(Message.MessageExchangeId -> "123"))
val result = producer.ask(message).get
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])
when("a test message causing an exception is sent to the producer with ?")
val message = Message("fail", Map(Message.MessageExchangeId -> "123"))
val result = producer.ask(message).as[Failure].get
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])
when("a test message is sent to the producer with !")
mockEndpoint.expectedBodiesReceived("received test")
val result = producer.tell(Message("test"), producer)
then("a normal response should have been sent")
mockEndpoint.assertIsSatisfied
}
}
private def mockEndpoint = CamelContextManager.mandatoryContext.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)
}
}
})
}
}
}

View file

@ -0,0 +1,101 @@
/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camel
import org.apache.camel.{ Exchange, Processor }
import org.apache.camel.builder.RouteBuilder
import org.apache.camel.component.mock.MockEndpoint
import akka.camel.TestSupport.SharedCamelSystem
import akka.actor.Props
import akka.pattern._
import akka.dispatch.Await
import akka.util.duration._
import org.scalatest._
class UntypedProducerTest extends WordSpec with BeforeAndAfterAll with BeforeAndAfterEach with SharedCamelSystem with GivenWhenThen {
import UntypedProducerTest._
val timeout = 1 second
override protected def beforeAll = {
camel.context.addRoutes(new TestRoute)
}
override protected def afterEach = {
mockEndpoint.reset
}
"An UntypedProducer producing a message to a sync Camel route" must {
"produce a message and receive a normal response" in {
given("a registered two-way producer")
val producer = system.actorOf(Props[SampleUntypedReplyingProducer])
when("a test message is sent to the producer with !!")
val message = CamelMessage("test", Map(CamelMessage.MessageExchangeId -> "123"))
val future = producer.ask(message)(timeout)
then("a normal response should have been returned by the producer")
val expected = CamelMessage("received test", Map(CamelMessage.MessageExchangeId -> "123"))
Await.result(future, timeout) match {
case result: CamelMessage assert(result === expected)
case unexpected fail("Actor responded with unexpected message:" + unexpected)
}
}
"produce a message and receive a failure response" in {
given("a registered two-way producer")
val producer = system.actorOf(Props[SampleUntypedReplyingProducer])
when("a test message causing an exception is sent to the producer with !!")
val message = CamelMessage("fail", Map(CamelMessage.MessageExchangeId -> "123"))
val future = producer.ask(message)(timeout)
then("a failure response should have been returned by the producer")
Await.result(future, timeout) match {
case result: Failure {
val expectedFailureText = result.cause.getMessage
val expectedHeaders = result.headers
assert(expectedFailureText === "failure")
assert(expectedHeaders === Map(CamelMessage.MessageExchangeId -> "123"))
}
case unexpected fail("Actor responded with unexpected message:" + unexpected)
}
}
}
"An UntypedProducer producing a message to a sync Camel route and then forwarding the response" must {
"produce a message and send a normal response to direct:forward-test-1" in {
given("a registered one-way producer configured with a forward target")
val producer = system.actorOf(Props[SampleUntypedForwardingProducer])
when("a test message is sent to the producer with !")
mockEndpoint.expectedBodiesReceived("received test")
val result = producer.tell(CamelMessage("test", Map[String, Any]()), producer)
then("a normal response should have been sent")
mockEndpoint.assertIsSatisfied
}
}
private def mockEndpoint = camel.context.getEndpoint("mock:mock", classOf[MockEndpoint])
}
object UntypedProducerTest {
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)
}
}
})
}
}
}

View file

@ -1,129 +0,0 @@
package 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 akka.actor.{ Actor, Props, Timeout }
import akka.actor.Actor._
import akka.camel.{ Failure, Message, CamelContextManager }
import akka.camel.CamelTestSupport._
class ActorComponentFeatureTest extends FeatureSpec with BeforeAndAfterAll with BeforeAndAfterEach {
import ActorComponentFeatureTest._
override protected def beforeAll = {
Actor.registry.local.shutdownAll
CamelContextManager.init
CamelContextManager.mandatoryContext.addRoutes(new TestRoute)
CamelContextManager.start
}
override protected def afterAll = CamelContextManager.stop
override protected def afterEach = {
Actor.registry.local.shutdownAll
mockEndpoint.reset
}
feature("Communicate with an actor via an actor:uuid endpoint") {
import CamelContextManager.mandatoryTemplate
scenario("one-way communication") {
val actor = actorOf(Props[Tester1]
val latch = (actor ? SetExpectedMessageCount(1)).as[CountDownLatch].get
mandatoryTemplate.sendBody("actor:uuid:%s" format actor.uuid, "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(Props[Tester2]
assert(mandatoryTemplate.requestBody("actor:uuid:%s" format actor.uuid, "Martin") === "Hello Martin")
}
scenario("two-way communication with timeout") {
val actor = actorOf(Props[Tester3].withTimeout(Timeout(1)))
intercept[RuntimeCamelException] {
mandatoryTemplate.requestBody("actor:uuid:%s?blocking=true" format actor.uuid, "Martin")
}
}
scenario("two-way communication via a custom route with failure response") {
mockEndpoint.expectedBodiesReceived("whatever")
mandatoryTemplate.requestBody("direct:failure-test-1", "whatever")
mockEndpoint.assertIsSatisfied
}
scenario("two-way communication via a custom route with exception") {
mockEndpoint.expectedBodiesReceived("whatever")
mandatoryTemplate.requestBody("direct:failure-test-2", "whatever")
mockEndpoint.assertIsSatisfied
}
}
feature("Communicate with an actor via an actor:id endpoint") {
import CamelContextManager.mandatoryTemplate
scenario("one-way communication") {
val actor = actorOf(Props[Tester1]
val latch = (actor ? SetExpectedMessageCount(1)).as[CountDownLatch].get
mandatoryTemplate.sendBody("actor:%s" format actor.address, "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(Props[Tester2]
assert(mandatoryTemplate.requestBody("actor:%s" format actor.address, "Martin") === "Hello Martin")
}
scenario("two-way communication via a custom route") {
val actor = actorOf(Props[CustomIdActor]("custom-id")
assert(mandatoryTemplate.requestBody("direct:custom-id-test-1", "Martin") === "Received Martin")
assert(mandatoryTemplate.requestBody("direct:custom-id-test-2", "Martin") === "Received Martin")
}
}
private def mockEndpoint = CamelContextManager.mandatoryContext.getEndpoint("mock:mock", classOf[MockEndpoint])
}
object ActorComponentFeatureTest {
class CustomIdActor extends Actor {
protected def receive = {
case msg: Message sender ! ("Received %s" format msg.body)
}
}
class FailWithMessage extends Actor {
protected def receive = {
case msg: Message sender ! 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(Props[FailWithMessage]
val failWithException = actorOf(Props[FailWithException]
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)
}
}
}

View file

@ -1,103 +0,0 @@
package akka.camel.component
import org.apache.camel.{ Endpoint, AsyncProcessor }
import org.apache.camel.impl.DefaultCamelContext
import org.junit._
import org.scalatest.junit.JUnitSuite
import akka.actor.uuidFrom
class ActorComponentTest extends JUnitSuite {
val component: ActorComponent = ActorComponentTest.actorComponent
def testUUID = "93da8c80-c3fd-11df-abed-60334b120057"
@Test
def shouldCreateEndpointWithIdDefined = {
val ep1: ActorEndpoint = component.createEndpoint("actor:abc").asInstanceOf[ActorEndpoint]
val ep2: ActorEndpoint = component.createEndpoint("actor:id:abc").asInstanceOf[ActorEndpoint]
assert(ep1.idValue === Some("abc"))
assert(ep2.idValue === Some("abc"))
assert(ep1.idType === "id")
assert(ep2.idType === "id")
assert(!ep1.blocking)
assert(!ep2.blocking)
assert(ep1.autoack)
assert(ep2.autoack)
}
@Test
def shouldCreateEndpointWithIdTemplate = {
val ep: ActorEndpoint = component.createEndpoint("actor:id:").asInstanceOf[ActorEndpoint]
assert(ep.idValue === None)
assert(ep.idType === "id")
assert(!ep.blocking)
assert(ep.autoack)
}
@Test
def shouldCreateEndpointWithIdTemplateAndBlockingSet = {
val ep: ActorEndpoint = component.createEndpoint("actor:id:?blocking=true").asInstanceOf[ActorEndpoint]
assert(ep.idValue === None)
assert(ep.idType === "id")
assert(ep.blocking)
assert(ep.autoack)
}
@Test
def shouldCreateEndpointWithUuidDefined = {
val ep: ActorEndpoint = component.createEndpoint("actor:uuid:%s" format testUUID).asInstanceOf[ActorEndpoint]
assert(ep.idValue === Some(testUUID))
assert(ep.idType === "uuid")
assert(!ep.blocking)
assert(ep.autoack)
}
@Test
def shouldCreateEndpointWithUuidTemplate = {
val ep: ActorEndpoint = component.createEndpoint("actor:uuid:").asInstanceOf[ActorEndpoint]
assert(ep.idValue === None)
assert(ep.idType === "uuid")
assert(!ep.blocking)
assert(ep.autoack)
}
@Test
def shouldCreateEndpointWithUuidTemplateAndBlockingSet = {
val ep: ActorEndpoint = component.createEndpoint("actor:uuid:?blocking=true").asInstanceOf[ActorEndpoint]
assert(ep.idValue === None)
assert(ep.idType === "uuid")
assert(ep.blocking)
assert(ep.autoack)
}
@Test
def shouldCreateEndpointWithBlockingSet = {
val ep: ActorEndpoint = component.createEndpoint("actor:uuid:%s?blocking=true" format testUUID).asInstanceOf[ActorEndpoint]
assert(ep.idValue === Some(testUUID))
assert(ep.idType === "uuid")
assert(ep.blocking)
assert(ep.autoack)
}
@Test
def shouldCreateEndpointWithAutoackUnset = {
val ep: ActorEndpoint = component.createEndpoint("actor:uuid:%s?autoack=false" format testUUID).asInstanceOf[ActorEndpoint]
assert(ep.idValue === Some(testUUID))
assert(ep.idType === "uuid")
assert(!ep.blocking)
assert(!ep.autoack)
}
}
object ActorComponentTest {
def actorComponent = {
val component = new ActorComponent
component.setCamelContext(new DefaultCamelContext)
component
}
def actorEndpoint(uri: String) = actorComponent.createEndpoint(uri)
def actorProducer(endpoint: Endpoint) = endpoint.createProducer
def actorAsyncProducer(endpoint: Endpoint) = endpoint.createProducer.asInstanceOf[AsyncProcessor]
}

View file

@ -1,254 +0,0 @@
package akka.camel.component
import ActorComponentTest._
import java.util.concurrent.{ CountDownLatch, TimeoutException, TimeUnit }
import org.apache.camel.{ AsyncCallback, ExchangePattern }
import org.junit.{ After, Test }
import org.scalatest.junit.JUnitSuite
import org.scalatest.BeforeAndAfterAll
import akka.actor.{ Props, Timeout }
import akka.actor.Actor._
import akka.camel.{ Failure, Message }
import akka.camel.CamelTestSupport._
class ActorProducerTest extends JUnitSuite with BeforeAndAfterAll {
import ActorProducerTest._
@After
def tearDown = registry.local.shutdownAll
@Test
def shouldSendMessageToActorWithSyncProcessor = {
val actor = actorOf(Props[Tester1]
val latch = (actor ? SetExpectedMessageCount(1)).as[CountDownLatch].get
val endpoint = actorEndpoint("actor:uuid:%s" format actor.uuid)
val exchange = endpoint.createExchange(ExchangePattern.InOnly)
exchange.getIn.setBody("Martin")
exchange.getIn.setHeader("k1", "v1")
actorProducer(endpoint).process(exchange)
assert(latch.await(5000, TimeUnit.MILLISECONDS))
val reply = (actor ? GetRetainedMessage).get.asInstanceOf[Message]
assert(reply.body === "Martin")
assert(reply.headers === Map(Message.MessageExchangeId -> exchange.getExchangeId, "k1" -> "v1"))
}
@Test
def shouldSendMessageToActorWithAsyncProcessor = {
val actor = actorOf(Props[Tester1]
val latch = (actor ? SetExpectedMessageCount(1)).as[CountDownLatch].get
val endpoint = actorEndpoint("actor:uuid:%s" format actor.uuid)
val exchange = endpoint.createExchange(ExchangePattern.InOnly)
exchange.getIn.setBody("Martin")
exchange.getIn.setHeader("k1", "v1")
actorAsyncProducer(endpoint).process(exchange, expectSyncCompletion)
assert(latch.await(5000, TimeUnit.MILLISECONDS))
val reply = (actor ? GetRetainedMessage).get.asInstanceOf[Message]
assert(reply.body === "Martin")
assert(reply.headers === Map(Message.MessageExchangeId -> exchange.getExchangeId, "k1" -> "v1"))
}
@Test
def shouldSendMessageToActorAndReceiveResponseWithSyncProcessor = {
val actor = actorOf(Props(new Tester2 {
override def response(msg: Message) = Message(super.response(msg), Map("k2" -> "v2"))
})
val endpoint = actorEndpoint("actor:uuid:%s" format actor.uuid)
val exchange = endpoint.createExchange(ExchangePattern.InOut)
exchange.getIn.setBody("Martin")
exchange.getIn.setHeader("k1", "v1")
actorProducer(endpoint).process(exchange)
assert(exchange.getOut.getBody === "Hello Martin")
assert(exchange.getOut.getHeader("k2") === "v2")
}
@Test
def shouldSendMessageToActorAndReceiveResponseWithAsyncProcessor = {
val actor = actorOf(Props(new Tester2 {
override def response(msg: Message) = Message(super.response(msg), Map("k2" -> "v2"))
})
val completion = expectAsyncCompletion
val endpoint = actorEndpoint("actor:uuid:%s" format actor.uuid)
val exchange = endpoint.createExchange(ExchangePattern.InOut)
exchange.getIn.setBody("Martin")
exchange.getIn.setHeader("k1", "v1")
actorAsyncProducer(endpoint).process(exchange, completion)
assert(completion.latch.await(5000, TimeUnit.MILLISECONDS))
assert(exchange.getOut.getBody === "Hello Martin")
assert(exchange.getOut.getHeader("k2") === "v2")
}
@Test
def shouldSendMessageToActorAndReceiveFailureWithAsyncProcessor = {
val actor = actorOf(Props(new Tester2 {
override def response(msg: Message) = Failure(new Exception("testmsg"), Map("k3" -> "v3"))
})
val completion = expectAsyncCompletion
val endpoint = actorEndpoint("actor:uuid:%s" format actor.uuid)
val exchange = endpoint.createExchange(ExchangePattern.InOut)
exchange.getIn.setBody("Martin")
exchange.getIn.setHeader("k1", "v1")
actorAsyncProducer(endpoint).process(exchange, completion)
assert(completion.latch.await(5000, TimeUnit.MILLISECONDS))
assert(exchange.getException.getMessage === "testmsg")
assert(exchange.getOut.getBody === null)
assert(exchange.getOut.getHeader("k3") === null) // headers from failure message are currently ignored
}
@Test
def shouldSendMessageToActorAndReceiveAckWithAsyncProcessor = {
val actor = actorOf(Props(new Tester2 {
override def response(msg: Message) = akka.camel.Ack
})
val completion = expectAsyncCompletion
val endpoint = actorEndpoint("actor:uuid:%s?autoack=false" format actor.uuid)
val exchange = endpoint.createExchange(ExchangePattern.InOnly)
exchange.getIn.setBody("Martin")
actorAsyncProducer(endpoint).process(exchange, completion)
assert(completion.latch.await(5000, TimeUnit.MILLISECONDS))
assert(exchange.getIn.getBody === "Martin")
assert(exchange.getOut.getBody === null)
}
@Test
def shouldDynamicallyRouteMessageToActorWithDefaultId = {
val actor1 = actorOf(Props[Tester1]("x")
val actor2 = actorOf(Props[Tester1]("y")
actor1
actor2
val latch1 = (actor1 ? SetExpectedMessageCount(1)).as[CountDownLatch].get
val latch2 = (actor2 ? SetExpectedMessageCount(1)).as[CountDownLatch].get
val endpoint = actorEndpoint("actor:id:%s" format actor1.address)
val exchange1 = endpoint.createExchange(ExchangePattern.InOnly)
val exchange2 = endpoint.createExchange(ExchangePattern.InOnly)
exchange1.getIn.setBody("Test1")
exchange2.getIn.setBody("Test2")
exchange2.getIn.setHeader(ActorComponent.ActorIdentifier, actor2.address)
actorProducer(endpoint).process(exchange1)
actorProducer(endpoint).process(exchange2)
assert(latch1.await(5, TimeUnit.SECONDS))
assert(latch2.await(5, TimeUnit.SECONDS))
val reply1 = (actor1 ? GetRetainedMessage).get.asInstanceOf[Message]
val reply2 = (actor2 ? GetRetainedMessage).get.asInstanceOf[Message]
assert(reply1.body === "Test1")
assert(reply2.body === "Test2")
}
@Test
def shouldDynamicallyRouteMessageToActorWithoutDefaultId = {
val actor1 = actorOf(Props[Tester1]("x")
val actor2 = actorOf(Props[Tester1]("y")
actor1
actor2
val latch1 = (actor1 ? SetExpectedMessageCount(1)).as[CountDownLatch].get
val latch2 = (actor2 ? SetExpectedMessageCount(1)).as[CountDownLatch].get
val endpoint = actorEndpoint("actor:id:")
val exchange1 = endpoint.createExchange(ExchangePattern.InOnly)
val exchange2 = endpoint.createExchange(ExchangePattern.InOnly)
exchange1.getIn.setBody("Test1")
exchange2.getIn.setBody("Test2")
exchange1.getIn.setHeader(ActorComponent.ActorIdentifier, actor1.address)
exchange2.getIn.setHeader(ActorComponent.ActorIdentifier, actor2.address)
actorProducer(endpoint).process(exchange1)
actorProducer(endpoint).process(exchange2)
assert(latch1.await(5, TimeUnit.SECONDS))
assert(latch2.await(5, TimeUnit.SECONDS))
val reply1 = (actor1 ? GetRetainedMessage).get.asInstanceOf[Message]
val reply2 = (actor2 ? GetRetainedMessage).get.asInstanceOf[Message]
assert(reply1.body === "Test1")
assert(reply2.body === "Test2")
}
@Test
def shouldDynamicallyRouteMessageToActorWithDefaultUuid = {
val actor1 = actorOf(Props[Tester1]
val actor2 = actorOf(Props[Tester1]
val latch1 = (actor1 ? SetExpectedMessageCount(1)).as[CountDownLatch].get
val latch2 = (actor2 ? SetExpectedMessageCount(1)).as[CountDownLatch].get
val endpoint = actorEndpoint("actor:uuid:%s" format actor1.uuid)
val exchange1 = endpoint.createExchange(ExchangePattern.InOnly)
val exchange2 = endpoint.createExchange(ExchangePattern.InOnly)
exchange1.getIn.setBody("Test1")
exchange2.getIn.setBody("Test2")
exchange2.getIn.setHeader(ActorComponent.ActorIdentifier, actor2.uuid.toString)
actorProducer(endpoint).process(exchange1)
actorProducer(endpoint).process(exchange2)
assert(latch1.await(5, TimeUnit.SECONDS))
assert(latch2.await(5, TimeUnit.SECONDS))
val reply1 = (actor1 ? GetRetainedMessage).get.asInstanceOf[Message]
val reply2 = (actor2 ? GetRetainedMessage).get.asInstanceOf[Message]
assert(reply1.body === "Test1")
assert(reply2.body === "Test2")
}
@Test
def shouldDynamicallyRouteMessageToActorWithoutDefaultUuid = {
val actor1 = actorOf(Props[Tester1]
val actor2 = actorOf(Props[Tester1]
val latch1 = (actor1 ? SetExpectedMessageCount(1)).as[CountDownLatch].get
val latch2 = (actor2 ? SetExpectedMessageCount(1)).as[CountDownLatch].get
val endpoint = actorEndpoint("actor:uuid:")
val exchange1 = endpoint.createExchange(ExchangePattern.InOnly)
val exchange2 = endpoint.createExchange(ExchangePattern.InOnly)
exchange1.getIn.setBody("Test1")
exchange2.getIn.setBody("Test2")
exchange1.getIn.setHeader(ActorComponent.ActorIdentifier, actor1.uuid)
exchange2.getIn.setHeader(ActorComponent.ActorIdentifier, actor2.uuid.toString)
actorProducer(endpoint).process(exchange1)
actorProducer(endpoint).process(exchange2)
assert(latch1.await(5, TimeUnit.SECONDS))
assert(latch2.await(5, TimeUnit.SECONDS))
val reply1 = (actor1 ? GetRetainedMessage).get.asInstanceOf[Message]
val reply2 = (actor2 ? GetRetainedMessage).get.asInstanceOf[Message]
assert(reply1.body === "Test1")
assert(reply2.body === "Test2")
}
@Test
def shouldThrowExceptionWhenIdNotSet{
val actor = actorOf(Props[Tester1]
val latch = (actor ? SetExpectedMessageCount(1)).as[CountDownLatch].get
val endpoint = actorEndpoint("actor:id:")
intercept[ActorIdentifierNotSetException] {
actorProducer(endpoint).process(endpoint.createExchange(ExchangePattern.InOnly))
}
}
@Test
def shouldThrowExceptionWhenUuidNotSet{
val actor = actorOf(Props[Tester1]
val latch = (actor ? SetExpectedMessageCount(1)).as[CountDownLatch].get
val endpoint = actorEndpoint("actor:uuid:")
intercept[ActorIdentifierNotSetException] {
actorProducer(endpoint).process(endpoint.createExchange(ExchangePattern.InOnly))
}
}
@Test
def shouldSendMessageToActorAndTimeout() {
val actor = actorOf(Props[Tester3].withTimeout(Timeout(1)))
val endpoint = actorEndpoint("actor:uuid:%s" format actor.uuid)
val exchange = endpoint.createExchange(ExchangePattern.InOut)
exchange.getIn.setBody("Martin")
intercept[TimeoutException] {
endpoint.createProducer.process(exchange)
}
}
}
object ActorProducerTest {
def expectSyncCompletion = new AsyncCallback {
def done(doneSync: Boolean) = assert(doneSync)
}
def expectAsyncCompletion = new AsyncCallback {
val latch = new CountDownLatch(1);
def done(doneSync: Boolean) = {
assert(!doneSync)
latch.countDown
}
}
}

View file

@ -0,0 +1,136 @@
package akka.camel.internal
import org.scalatest.matchers.MustMatchers
import akka.testkit.{ TestProbe, TestKit }
import akka.util.duration._
import org.scalatest.{ GivenWhenThen, BeforeAndAfterEach, BeforeAndAfterAll, WordSpec }
import akka.actor.{ Props, ActorSystem }
import akka.util.Duration
class ActivationTrackerTest extends TestKit(ActorSystem("test")) with WordSpec with MustMatchers with BeforeAndAfterAll with BeforeAndAfterEach with GivenWhenThen {
override protected def afterAll() { system.shutdown() }
var actor: TestProbe = _
var awaiting: Awaiting = _
var anotherAwaiting: Awaiting = _
val cause = new Exception("cause of failure")
override protected def beforeEach() {
actor = TestProbe()
awaiting = new Awaiting(actor)
anotherAwaiting = new Awaiting(actor)
}
val at = system.actorOf(Props[ActivationTracker])
"ActivationTracker forwards activation message to all awaiting parties" in {
awaiting.awaitActivation()
anotherAwaiting.awaitActivation()
publish(EndpointActivated(actor.ref))
awaiting.verifyActivated()
anotherAwaiting.verifyActivated()
}
"ActivationTracker send activation message even if activation happened earlier" in {
publish(EndpointActivated(actor.ref))
Thread.sleep(50)
awaiting.awaitActivation()
awaiting.verifyActivated()
}
"ActivationTracker send activation message even if actor is already deactivated" in {
publish(EndpointActivated(actor.ref))
publish(EndpointDeActivated(actor.ref))
Thread.sleep(50)
awaiting.awaitActivation()
awaiting.verifyActivated()
}
"ActivationTracker forwards de-activation message to all awaiting parties" in {
given("Actor is activated")
publish(EndpointActivated(actor.ref))
given("Actor is deactivated")
publish(EndpointDeActivated(actor.ref))
when("Multiple parties await deactivation")
awaiting.awaitDeActivation()
anotherAwaiting.awaitDeActivation()
then("all awaiting parties are notified")
awaiting.verifyDeActivated()
anotherAwaiting.verifyDeActivated()
}
"ActivationTracker forwards de-activation message even if deactivation happened earlier" in {
given("Actor is activated")
publish(EndpointActivated(actor.ref))
given("Someone is awaiting de-activation")
awaiting.awaitDeActivation()
when("Actor is de-activated")
publish(EndpointDeActivated(actor.ref))
then("Awaiting gets notified")
awaiting.verifyDeActivated()
}
"ActivationTracker forwards de-activation message even if someone awaits de-activation even before activation happens" in {
given("Someone is awaiting de-activation")
val awaiting = new Awaiting(actor)
awaiting.awaitDeActivation()
given("Actor is activated")
publish(EndpointActivated(actor.ref))
when("Actor is de-activated")
publish(EndpointDeActivated(actor.ref))
then("Awaiting gets notified")
awaiting.verifyDeActivated()
}
"ActivationTracker sends activation failure when failed to activate" in {
awaiting.awaitActivation()
publish(EndpointFailedToActivate(actor.ref, cause))
awaiting.verifyFailedToActivate()
}
"ActivationTracker sends de-activation failure when failed to de-activate" in {
publish(EndpointActivated(actor.ref))
awaiting.awaitDeActivation()
publish(EndpointFailedToDeActivate(actor.ref, cause))
awaiting.verifyFailedToDeActivate()
}
"ActivationTracker sends activation message even if it failed to de-activate" in {
publish(EndpointActivated(actor.ref))
publish(EndpointFailedToDeActivate(actor.ref, cause))
awaiting.awaitActivation()
awaiting.verifyActivated()
}
def publish(msg: AnyRef) = system.eventStream.publish(msg)
class Awaiting(actor: TestProbe) {
val probe = TestProbe()
def awaitActivation() = at.tell(AwaitActivation(actor.ref), probe.ref)
def awaitDeActivation() = at.tell(AwaitDeActivation(actor.ref), probe.ref)
def verifyActivated(timeout: Duration = 50 millis) = within(timeout) { probe.expectMsg(EndpointActivated(actor.ref)) }
def verifyDeActivated(timeout: Duration = 50 millis) = within(timeout) { probe.expectMsg(EndpointDeActivated(actor.ref)) }
def verifyFailedToActivate(timeout: Duration = 50 millis) = within(timeout) { probe.expectMsg(EndpointFailedToActivate(actor.ref, cause)) }
def verifyFailedToDeActivate(timeout: Duration = 50 millis) = within(timeout) { probe.expectMsg(EndpointFailedToDeActivate(actor.ref, cause)) }
}
}

View file

@ -0,0 +1,27 @@
/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camel.internal.component
import org.scalatest.matchers.MustMatchers
import akka.util.duration._
import akka.camel.TestSupport.SharedCamelSystem
import org.apache.camel.Component
import org.scalatest.WordSpec
class ActorComponentConfigurationTest extends WordSpec with MustMatchers with SharedCamelSystem {
val component: Component = camel.context.getComponent("actor")
"Endpoint url config must be correctly parsed" in {
val actorEndpointConfig = component.createEndpoint("actor://path:akka://test/user/$a?autoack=false&replyTimeout=987000000+nanos").asInstanceOf[ActorEndpointConfig]
actorEndpointConfig must have(
'endpointUri("actor://path:akka://test/user/$a?autoack=false&replyTimeout=987000000+nanos"),
'path(ActorEndpointPath.fromCamelPath("path:akka://test/user/$a")),
'autoack(false),
'replyTimeout(987000000 nanos))
}
}

View file

@ -0,0 +1,27 @@
/**
* Copyright (C) 2009when2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camel.internal.component
import org.scalatest.mock.MockitoSugar
import org.scalatest.matchers.MustMatchers
import akka.camel.TestSupport.SharedCamelSystem
import akka.actor.Props
import org.scalatest.WordSpec
class ActorEndpointPathTest extends WordSpec with SharedCamelSystem with MustMatchers with MockitoSugar {
def find(path: String) = ActorEndpointPath.fromCamelPath("path:" + path).findActorIn(system)
"findActorIn returns Some(actor ref) if actor exists" in {
val path = system.actorOf(Props(behavior = ctx { case _ {} })).path
find(path.toString) must be('defined)
}
"findActorIn returns None" when {
"invalid path" in { find("some_invalid_path") must be(None) }
"non existing valid path" in { find("akka://system/user/$a") must be(None) }
}
}

View file

@ -0,0 +1,340 @@
/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camel.internal.component
import org.scalatest.mock.MockitoSugar
import org.mockito.Matchers.{ eq the, any }
import org.mockito.Mockito._
import org.apache.camel.AsyncCallback
import java.util.concurrent.atomic.AtomicBoolean
import akka.util.duration._
import akka.util.Duration
import akka.testkit.{ TestKit, TestProbe }
import java.lang.String
import akka.actor.{ ActorRef, Props, ActorSystem, Actor }
import akka.camel._
import internal.CamelExchangeAdapter
import org.scalatest.{ Suite, WordSpec, BeforeAndAfterAll, BeforeAndAfterEach }
import akka.camel.TestSupport._
import java.util.concurrent.{ TimeoutException, CountDownLatch, TimeUnit }
import org.mockito.{ ArgumentMatcher, Matchers, Mockito }
import org.scalatest.matchers.MustMatchers
class ActorProducerTest extends TestKit(ActorSystem("test")) with WordSpec with MustMatchers with ActorProducerFixture {
"ActorProducer" when {
"synchronous" when {
"consumer actor doesnt exist" must {
"set failure message on exchange" in {
producer = given(actor = null)
producer.processExchangeAdapter(exchange)
verify(exchange).setFailure(any[Failure])
}
}
"in-only" must {
def producer = given(outCapable = false)
"pass the message to the consumer" in {
producer.processExchangeAdapter(exchange)
within(1 second)(probe.expectMsg(message))
}
"not expect response and not block" in {
time(producer.processExchangeAdapter(exchange)) must be < (30 millis)
}
}
"out capable" when {
"response is sent back by actor" must {
"get a response" in {
producer = given(actor = echoActor, outCapable = true)
producer.processExchangeAdapter(exchange)
verify(exchange).setResponse(msg("received " + message))
}
}
"response is not sent by actor" must {
def process() = {
producer = given(outCapable = true, replyTimeout = 100 millis)
time(producer.processExchangeAdapter(exchange))
}
"timeout after replyTimeout" in {
val duration = process()
duration must (be >= (100 millis) and be < (300 millis))
}
"never set the response on exchange" in {
process()
verify(exchange, Mockito.never()).setResponse(any[CamelMessage])
}
"set failure message to timeout" in {
process()
verify(exchange).setFailure(any[Failure])
}
}
}
//TODO: write more tests for synchronous process(exchange) method
}
"asynchronous" when {
def verifyFailureIsSet {
producer.processExchangeAdapter(exchange, asyncCallback)
asyncCallback.awaitCalled()
verify(exchange).setFailure(any[Failure])
}
"out-capable" when {
"consumer actor doesnt exist" must {
"set failure message on exchange" in {
producer = given(actor = null, outCapable = true)
verifyFailureIsSet
}
}
"response is ok" must {
"get a response and async callback as soon as it gets the response (but not before)" in {
producer = given(outCapable = true)
val doneSync = producer.processExchangeAdapter(exchange, asyncCallback)
asyncCallback.expectNoCallWithin(100 millis); info("no async callback before response")
within(1 second) {
probe.expectMsgType[CamelMessage]
probe.sender ! "some message"
}
doneSync must be(false); info("done async")
asyncCallback.expectDoneAsyncWithin(1 second); info("async callback received")
verify(exchange).setResponse(msg("some message")); info("response as expected")
}
}
"response is Failure" must {
"set an exception on exchange" in {
val failure = Failure(new RuntimeException("some failure"))
producer = given(outCapable = true)
producer.processExchangeAdapter(exchange, asyncCallback)
within(1 second) {
probe.expectMsgType[CamelMessage]
probe.sender ! failure
asyncCallback.awaitCalled(remaining)
}
verify(exchange).setFailure(failure)
}
}
"no response is sent within timeout" must {
"set TimeoutException on exchange" in {
producer = given(outCapable = true, replyTimeout = 10 millis)
producer.processExchangeAdapter(exchange, asyncCallback)
asyncCallback.awaitCalled(100 millis)
verify(exchange).setFailure(Matchers.argThat(new ArgumentMatcher[Failure] {
def matches(failure: AnyRef) = { failure.asInstanceOf[Failure].getCause must be(anInstanceOf[TimeoutException]); true }
}))
}
}
}
"in-only" when {
"consumer actor doesnt exist" must {
"set failure message on exchange" in {
producer = given(actor = null, outCapable = false)
verifyFailureIsSet
}
}
"autoAck" must {
"get sync callback as soon as it sends a message" in {
producer = given(outCapable = false, autoAck = true)
val doneSync = producer.processExchangeAdapter(exchange, asyncCallback)
doneSync must be(true); info("done sync")
asyncCallback.expectDoneSyncWithin(1 second); info("async callback called")
verify(exchange, never()).setResponse(any[CamelMessage]); info("no response forwarded to exchange")
}
}
"manualAck" when {
"response is Ack" must {
"get async callback" in {
producer = given(outCapable = false, autoAck = false)
val doneSync = producer.processExchangeAdapter(exchange, asyncCallback)
doneSync must be(false)
within(1 second) {
probe.expectMsgType[CamelMessage]; info("message sent to consumer")
probe.sender ! Ack
asyncCallback.expectDoneAsyncWithin(remaining); info("async callback called")
}
verify(exchange, never()).setResponse(any[CamelMessage]); info("no response forwarded to exchange")
}
}
"expecting Ack or Failure message and some other message is sent as a response" must {
"fail" in {
producer = given(outCapable = false, autoAck = false)
producer.processExchangeAdapter(exchange, asyncCallback)
within(1 second) {
probe.expectMsgType[CamelMessage]; info("message sent to consumer")
probe.sender ! "some neither Ack nor Failure response"
asyncCallback.expectDoneAsyncWithin(remaining); info("async callback called")
}
verify(exchange, never()).setResponse(any[CamelMessage]); info("no response forwarded to exchange")
verify(exchange).setFailure(any[Failure]); info("failure set")
}
}
"no Ack is sent within timeout" must {
"set failure on exchange" in {
producer = given(outCapable = false, replyTimeout = 10 millis, autoAck = false)
producer.processExchangeAdapter(exchange, asyncCallback)
asyncCallback.awaitCalled(100 millis)
verify(exchange).setFailure(any[Failure])
}
}
"response is Failure" must {
"set an exception on exchange" in {
producer = given(outCapable = false, autoAck = false)
val doneSync = producer.processExchangeAdapter(exchange, asyncCallback)
doneSync must be(false)
within(1 second) {
probe.expectMsgType[CamelMessage]; info("message sent to consumer")
probe.sender ! Failure(new Exception)
asyncCallback.awaitCalled(remaining);
}
verify(exchange, never()).setResponse(any[CamelMessage]); info("no response forwarded to exchange")
verify(exchange).setFailure(any[Failure]); info("failure set")
}
}
}
}
}
}
}
trait ActorProducerFixture extends MockitoSugar with BeforeAndAfterAll with BeforeAndAfterEach { self: TestKit with MustMatchers with Suite
var camel: Camel = _
var exchange: CamelExchangeAdapter = _
var callback: AsyncCallback = _
var producer: ActorProducer = _
var message: CamelMessage = _
var probe: TestProbe = _
var asyncCallback: TestAsyncCallback = _
var actorEndpointPath: ActorEndpointPath = _
var actorComponent: ActorComponent = _
override protected def beforeEach() {
asyncCallback = createAsyncCallback
probe = TestProbe()
camel = mock[Camel]
exchange = mock[CamelExchangeAdapter]
callback = mock[AsyncCallback]
actorEndpointPath = mock[ActorEndpointPath]
actorComponent = mock[ActorComponent]
producer = new ActorProducer(config(), camel)
message = CamelMessage(null, null)
}
override protected def afterAll() {
system.shutdown()
}
def msg(s: String) = CamelMessage(s, Map.empty)
def given(actor: ActorRef = probe.ref, outCapable: Boolean = true, autoAck: Boolean = true, replyTimeout: Duration = Int.MaxValue seconds) = {
prepareMocks(actor, outCapable = outCapable)
new ActorProducer(config(isAutoAck = autoAck, _replyTimeout = replyTimeout), camel)
}
def createAsyncCallback = new TestAsyncCallback
class TestAsyncCallback extends AsyncCallback {
def expectNoCallWithin(duration: Duration) {
if (callbackReceived.await(duration.toNanos, TimeUnit.NANOSECONDS)) fail("NOT expected callback, but received one!")
}
def awaitCalled(timeout: Duration = 1 second) { valueWithin(1 second) }
val callbackReceived = new CountDownLatch(1)
val callbackValue = new AtomicBoolean()
def done(doneSync: Boolean) {
callbackValue set doneSync
callbackReceived.countDown()
}
private[this] def valueWithin(implicit timeout: Duration) = {
if (!callbackReceived.await(timeout.toNanos, TimeUnit.NANOSECONDS)) fail("Callback not received!")
callbackValue.get
}
def expectDoneSyncWithin(implicit timeout: Duration) {
if (!valueWithin(timeout)) fail("Expected to be done Synchronously")
}
def expectDoneAsyncWithin(implicit timeout: Duration) {
if (valueWithin(timeout)) fail("Expected to be done Asynchronously")
}
}
def config(endpointUri: String = "test-uri", isAutoAck: Boolean = true, _replyTimeout: Duration = Int.MaxValue seconds) = {
val endpoint = new ActorEndpoint(endpointUri, actorComponent, actorEndpointPath, camel)
endpoint.autoack = isAutoAck
endpoint.replyTimeout = _replyTimeout
endpoint
}
def prepareMocks(actor: ActorRef, message: CamelMessage = message, outCapable: Boolean) {
when(actorEndpointPath.findActorIn(any[ActorSystem])) thenReturn Option(actor)
when(exchange.toRequestMessage(any[Map[String, Any]])) thenReturn message
when(exchange.isOutCapable) thenReturn outCapable
}
def echoActor = system.actorOf(Props(new Actor {
protected def receive = {
case msg sender ! "received " + msg
}
}))
}

View file

@ -0,0 +1,36 @@
/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.camel.internal.component
import org.scalatest.matchers.MustMatchers
import akka.util.duration._
import akka.util.Duration
import org.scalatest.WordSpec
class DurationConverterTest extends WordSpec with MustMatchers {
import DurationTypeConverter._
"DurationTypeConverter must convert '10 nanos'" in {
convertTo(classOf[Duration], "10 nanos") must be(10 nanos)
}
"DurationTypeConverter must do the roundtrip" in {
convertTo(classOf[Duration], DurationTypeConverter.toString(10 seconds)) must be(10 seconds)
}
"DurationTypeConverter must throw if invalid format" in {
intercept[Exception] {
convertTo(classOf[Duration], "abc nanos") must be(10 nanos)
}
}
"DurationTypeConverter must throw if doesn't end with nanos" in {
intercept[Exception] {
convertTo(classOf[Duration], "10233") must be(10 nanos)
}
}
}

View file

@ -0,0 +1,3 @@
akka{
loglevel = "ERROR"
}

View file

@ -16,7 +16,7 @@ class ClusterConfigSpec extends ClusterSpec {
"be able to parse generic cluster config elements" in {
val settings = new ClusterSettings(system.settings.config, system.name)
import settings._
FailureDetectorThreshold must be(8)
FailureDetectorThreshold must be(3)
FailureDetectorMaxSampleSize must be(1000)
NodeToJoin must be(None)
PeriodicTasksInitialDelay must be(1 seconds)

View file

@ -29,12 +29,12 @@ class NodeStartupSpec extends ClusterSpec with ImplicitSender {
val remote0 = system0.provider.asInstanceOf[RemoteActorRefProvider]
node0 = Node(system0)
"be a singleton cluster when started up" in {
"be a singleton cluster when started up" taggedAs LongRunningTest in {
Thread.sleep(1.seconds.dilated.toMillis)
node0.isSingletonCluster must be(true)
}
"be in 'Up' phase when started up" in {
"be in 'Joining' phase when started up" taggedAs LongRunningTest in {
val members = node0.latestGossip.members
val joiningMember = members find (_.address.port.get == 5550)
joiningMember must be('defined)
@ -43,7 +43,7 @@ class NodeStartupSpec extends ClusterSpec with ImplicitSender {
}
"A second cluster node with a 'node-to-join' config defined" must {
"join the other node cluster as 'Joining' when sending a Join command" in {
"join the other node cluster when sending a Join command" taggedAs LongRunningTest in {
system1 = ActorSystem("system1", ConfigFactory
.parseString("""
akka {
@ -55,11 +55,11 @@ class NodeStartupSpec extends ClusterSpec with ImplicitSender {
val remote1 = system1.provider.asInstanceOf[RemoteActorRefProvider]
node1 = Node(system1)
Thread.sleep(5.seconds.dilated.toMillis) // give enough time for node1 to JOIN node0
Thread.sleep(10.seconds.dilated.toMillis) // give enough time for node1 to JOIN node0 and leader to move him to UP
val members = node0.latestGossip.members
val joiningMember = members find (_.address.port.get == 5551)
joiningMember must be('defined)
joiningMember.get.status must be(MemberStatus.Joining)
joiningMember.get.status must be(MemberStatus.Up)
}
}
} catch {

View file

@ -13,9 +13,11 @@ Akka is using ``Scalariform`` to format the source code as part of the build. So
Process
-------
* Make sure you have signed the Akka CLA, if not, ask for it on the Mailing List.
* Pick a ticket, if there is no ticket for your work then create one first.
* Start working in a feature branch. Name it something like ``wip-<descriptive name or ticket number>-<your username>``.
* When you are done send a request for review to the Akka developer mailing list. If successfully review, merge into master.
* Start working in a feature branch. Name it something like ``wip-<ticket number>-<descriptive name>-<your username>``.
* When you are done, create a GitHub Pull-Request towards the targeted branch and email the Akka Mailing List that you want it reviewed
* When there's consensus on the review, someone from the Akka Core Team will merge it.
Commit messages
---------------

View file

@ -109,6 +109,23 @@ you might want to know how to serialize and deserialize them properly, here's th
.. includecode:: code/akka/docs/serialization/SerializationDocTestBase.java
:include: imports,actorref-serializer
Deep serialization of Actors
----------------------------
The current recommended approach to do deep serialization of internal actor state is to use Event Sourcing,
for more reading on the topic, see these examples:
`Martin Krasser on EventSourcing Part1 <http://krasserm.blogspot.com/2011/11/building-event-sourced-web-application.html>`_
`Martin Krasser on EventSourcing Part2 <http://krasserm.blogspot.com/2012/01/building-event-sourced-web-application.html>`_
.. note::
Built-in API support for persisting Actors will come in a later release, see the roadmap for more info:
`Akka 2.0 roadmap <https://docs.google.com/a/typesafe.com/document/d/18W9-fKs55wiFNjXL9q50PYOnR7-nnsImzJqHOPPbM4E>`_
A Word About Java Serialization
===============================

View file

@ -287,6 +287,13 @@ Messages are sent to an Actor through one of the following methods.
Message ordering is guaranteed on a per-sender basis.
.. note::
There are performance implications of using ``ask`` since something needs to
keep track of when it times out, there needs to be something that bridges
a ``Promise`` into an ``ActorRef`` and it also needs to be reachable through
remoting. So always prefer ``tell`` for performance, and only ``ask`` if you must.
In all these methods you have the option of passing along your own ``ActorRef``.
Make it a practice of doing so because it will allow the receiver actors to be able to respond
to your message, since the sender reference is sent along with the message.

View file

@ -12,7 +12,8 @@ Release Versions
2.0
-----
- Akka 2.0 - http://doc.akka.io/api/akka/2.0/
- Akka - http://doc.akka.io/api/akka/2.0/
1.3.1
-----

View file

@ -331,6 +331,13 @@ Messages are sent to an Actor through one of the following methods.
Message ordering is guaranteed on a per-sender basis.
.. note::
There are performance implications of using ``ask`` since something needs to
keep track of when it times out, there needs to be something that bridges
a ``Promise`` into an ``ActorRef`` and it also needs to be reachable through
remoting. So always prefer ``tell`` for performance, and only ``ask`` if you must.
Tell: Fire-forget
-----------------

View file

@ -107,6 +107,23 @@ you might want to know how to serialize and deserialize them properly, here's th
.. includecode:: code/akka/docs/serialization/SerializationDocSpec.scala
:include: imports,actorref-serializer
Deep serialization of Actors
----------------------------
The current recommended approach to do deep serialization of internal actor state is to use Event Sourcing,
for more reading on the topic, see these examples:
`Martin Krasser on EventSourcing Part1 <http://krasserm.blogspot.com/2011/11/building-event-sourced-web-application.html>`_
`Martin Krasser on EventSourcing Part2 <http://krasserm.blogspot.com/2012/01/building-event-sourced-web-application.html>`_
.. note::
Built-in API support for persisting Actors will come in a later release, see the roadmap for more info:
`Akka 2.0 roadmap <https://docs.google.com/a/typesafe.com/document/d/18W9-fKs55wiFNjXL9q50PYOnR7-nnsImzJqHOPPbM4E>`_
A Word About Java Serialization
===============================

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