Processed review wip-camel pull request 344

This commit is contained in:
RayRoestenburg 2012-03-01 17:32:10 +01:00
parent 4d6511c5c6
commit f74616f828
56 changed files with 544 additions and 1992 deletions

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

@ -11,15 +11,21 @@ import java.util.concurrent.TimeoutException
import akka.actor.{ ActorSystem, Props, ActorRef } import akka.actor.{ ActorSystem, Props, ActorRef }
import akka.pattern._ 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 { trait Activation {
import akka.dispatch.Await import akka.dispatch.Await
def system: ActorSystem def system: ActorSystem
private[camel] val activationTracker = system.actorOf(Props[ActivationTracker])
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. * 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. * @throws akka.camel.ActivationTimeoutException if endpoint is not activated within timeout.
*/ */
def awaitActivation(endpoint: ActorRef, timeout: Duration): ActorRef = { def awaitActivation(endpoint: ActorRef, timeout: Duration): ActorRef = {
@ -32,7 +38,8 @@ trait Activation {
/** /**
* Awaits for endpoint to be de-activated. It is blocking until endpoint is unregistered in camel context or timeout expires. * 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. * @throws akka.camel.DeActivationTimeoutException if endpoint is not de-activated within timeout.
*/ */
def awaitDeactivation(endpoint: ActorRef, timeout: Duration) { def awaitDeactivation(endpoint: ActorRef, timeout: Duration) {
@ -44,7 +51,9 @@ trait Activation {
} }
/** /**
* Similar to `awaitActivation` but returns future instead. * 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] = { def activationFutureFor(endpoint: ActorRef, timeout: Duration): Future[ActorRef] = {
(activationTracker.ask(AwaitActivation(endpoint))(Timeout(timeout))).map[ActorRef] { (activationTracker.ask(AwaitActivation(endpoint))(Timeout(timeout))).map[ActorRef] {
@ -54,20 +63,32 @@ trait Activation {
} }
/** /**
* Similar to awaitDeactivation but returns future instead. * 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] = { def deactivationFutureFor(endpoint: ActorRef, timeout: Duration): Future[Unit] = {
(activationTracker.ask(AwaitDeActivation(endpoint))(Timeout(timeout))).map[Unit] { (activationTracker.ask(AwaitDeActivation(endpoint))(Timeout(timeout))).map[Unit] {
case EndpointDeActivated(_) {} case EndpointDeActivated(_) ()
case EndpointFailedToDeActivate(_, cause) throw cause 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 { 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) 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 { 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) override def getMessage = "Timed out after %s, while waiting for activation of %s" format (timeout, endpoint.path)
} }

View file

@ -11,6 +11,7 @@ import org.apache.camel.{ ProducerTemplate, CamelContext }
//TODO complete this doc //TODO complete this doc
/** /**
* Camel trait encapsulates the underlying camel machinery. * 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 { trait Camel extends ConsumerRegistry with ProducerRegistry with Extension with Activation {
@ -30,30 +31,6 @@ trait Camel extends ConsumerRegistry with ProducerRegistry with Extension with A
*/ */
def template: ProducerTemplate def template: ProducerTemplate
/**
* Associated `ActorSystem`.
*
* <p>It can be used to start producers, consumers or any other actors which need to interact with camel,
* for example:
* {{{
* val system = ActorSystem("test")
* system.actorOf(Props[SysOutConsumer])
*
* class SysOutConsumer extends Consumer {
* def endpointUri = "file://data/input/CamelConsumer"
*
* protected def receive = {
* case msg: Message {
* printf("Received '%s'\\n", msg.bodyAs[String])
* }
* }
* }
* }}}
* '''Note:''' This actor system is responsible for stopping the underlying camel instance.
*
* @see [[akka.camel.CamelExtension]]
*/
def system: ActorSystem
} }
/** /**
@ -75,10 +52,12 @@ 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. * Creates a new instance of Camel and makes sure it gets stopped when the actor system is shutdown.
*/ */
def createExtension(system: ExtendedActorSystem) = { def createExtension(system: ExtendedActorSystem) = {
val camel = new DefaultCamel(system).start; val camel = new DefaultCamel(system).start
system.registerOnTermination(camel.shutdown()) system.registerOnTermination(camel.shutdown())
camel camel
} }
def lookup() = CamelExtension def lookup(): ExtensionId[Camel] = CamelExtension
override def get(system: ActorSystem): Camel = super.get(system)
} }

View file

@ -9,18 +9,18 @@ import java.util.{ Map ⇒ JMap, Set ⇒ JSet }
import scala.collection.JavaConversions._ import scala.collection.JavaConversions._
import akka.japi.{ Function JFunction } import akka.japi.{ Function JFunction }
import org.apache.camel.{ CamelContext, Message CamelMessage } import org.apache.camel.{ CamelContext, Message JCamelMessage }
/** /**
* An immutable representation of a Camel message. * An immutable representation of a Camel message.
* *
* @author Martin Krasser * @author Martin Krasser
*/ */
case class Message(body: Any, headers: Map[String, Any]) { case class CamelMessage(body: Any, headers: Map[String, Any]) {
def this(body: Any, headers: JMap[String, Any]) = this(body, headers.toMap) //for Java def this(body: Any, headers: JMap[String, Any]) = this(body, headers.toMap) //for Java
override def toString = "Message(%s, %s)" format (body, headers) override def toString = "CamelMessage(%s, %s)" format (body, headers)
/** /**
* Returns those headers from this message whose name is contained in <code>names</code>. * Returns those headers from this message whose name is contained in <code>names</code>.
@ -60,73 +60,71 @@ case class Message(body: Any, headers: Map[String, Any]) {
def getHeader(name: String): Any = headers(name) def getHeader(name: String): Any = headers(name)
/** /**
* Creates a Message with a transformed body using a <code>transformer</code> function. * Creates a CamelMessage with a transformed body using a <code>transformer</code> function.
*/ */
def mapBody[A, B](transformer: A B): Message = withBody(transformer(body.asInstanceOf[A])) def mapBody[A, B](transformer: A B): CamelMessage = withBody(transformer(body.asInstanceOf[A]))
/** /**
* Creates a Message with a transformed body using a <code>transformer</code> function. * Creates a CamelMessage with a transformed body using a <code>transformer</code> function.
* <p> * <p>
* Java API * Java API
*/ */
def mapBody[A, B](transformer: JFunction[A, B]): Message = withBody(transformer(body.asInstanceOf[A])) def mapBody[A, B](transformer: JFunction[A, B]): CamelMessage = withBody(transformer(body.asInstanceOf[A]))
/** /**
* Creates a Message with a given <code>body</code>. * Creates a CamelMessage with a given <code>body</code>.
*/ */
def withBody(body: Any) = Message(body, this.headers) def withBody(body: Any) = CamelMessage(body, this.headers)
/** /**
* Creates a new Message with given <code>headers</code>. * Creates a new CamelMessage with given <code>headers</code>.
*/ */
def withHeaders[A](headers: Map[String, A]): Message = copy(this.body, headers) def withHeaders[A](headers: Map[String, A]): CamelMessage = copy(this.body, headers)
/** /**
* Creates a new Message with given <code>headers</code>. A copy of the headers map is made. * Creates a new CamelMessage with given <code>headers</code>. A copy of the headers map is made.
* <p> * <p>
* Java API * Java API
*/ */
def withHeaders[A](headers: JMap[String, A]): Message = withHeaders(headers.toMap) def withHeaders[A](headers: JMap[String, A]): CamelMessage = withHeaders(headers.toMap)
/** /**
* Creates a new Message with given <code>headers</code> added to the current headers. * Creates a new CamelMessage with given <code>headers</code> added to the current headers.
*/ */
def plusHeaders[A](headers: Map[String, A]): Message = copy(this.body, this.headers ++ headers) def addHeaders[A](headers: Map[String, A]): CamelMessage = copy(this.body, this.headers ++ headers)
/** /**
* Creates a new Message with given <code>headers</code> added to the current headers. * Creates a new CamelMessage with given <code>headers</code> added to the current headers.
* A copy of the headers map is made. * A copy of the headers map is made.
* <p> * <p>
* Java API * Java API
*/ */
def plusHeaders[A](headers: JMap[String, A]): Message = plusHeaders(headers.toMap) def addHeaders[A](headers: JMap[String, A]): CamelMessage = addHeaders(headers.toMap)
/** /**
* Creates a new Message with the given <code>header</code> added to the current headers. * Creates a new CamelMessage with the given <code>header</code> added to the current headers.
*/ */
def plusHeader(header: (String, Any)): Message = copy(this.body, this.headers + header) def addHeader(header: (String, Any)): CamelMessage = copy(this.body, this.headers + header)
/** /**
* Creates a new Message with the given header, represented by <code>name</code> and * Creates a new CamelMessage with the given header, represented by <code>name</code> and
* <code>value</code> added to the existing headers. * <code>value</code> added to the existing headers.
* <p> * <p>
* Java API * Java API
*/ */
def plusHeader(name: String, value: Any): Message = plusHeader((name, value)) def addHeader(name: String, value: Any): CamelMessage = addHeader((name, value))
/** /**
* Creates a new Message where the header with given <code>headerName</code> is removed from * Creates a new CamelMessage where the header with given <code>headerName</code> is removed from
* the existing headers. * the existing headers.
*/ */
def withoutHeader(headerName: String) = copy(this.body, this.headers - headerName) def withoutHeader(headerName: String) = copy(this.body, this.headers - headerName)
def copyContentTo(to: CamelMessage) = { def copyContentTo(to: JCamelMessage) = {
to.setBody(this.body) to.setBody(this.body)
for ((name, value) this.headers) to.getHeaders.put(name, value.asInstanceOf[AnyRef]) for ((name, value) this.headers) to.getHeaders.put(name, value.asInstanceOf[AnyRef])
} }
}
class RichMessage(message: Message, camelContext: CamelContext) {
/** /**
* Returns the body of the message converted to the type <code>T</code>. Conversion is done * 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 * using Camel's type converter. The type converter is obtained from the CamelContext managed
@ -136,7 +134,7 @@ class RichMessage(message: Message, camelContext: CamelContext) {
* @see CamelContextManager. * @see CamelContextManager.
*/ */
def bodyAs[T](implicit m: Manifest[T]): T = getBodyAs(m.erasure.asInstanceOf[Class[T]]) 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> * Returns the body of the message converted to the type as given by the <code>clazz</code>
@ -148,26 +146,26 @@ class RichMessage(message: Message, camelContext: CamelContext) {
* *
* @see CamelContextManager. * @see CamelContextManager.
*/ */
def getBodyAs[T](clazz: Class[T]): T = def getBodyAs[T](clazz: Class[T], camelContext: CamelContext): T =
camelContext.getTypeConverter.mandatoryConvertTo[T](clazz, message.body) camelContext.getTypeConverter.mandatoryConvertTo[T](clazz, body)
/** /**
* Creates a Message with current <code>body</code> converted to type <code>T</code>. * Creates a CamelMessage with current <code>body</code> converted to type <code>T</code>.
*/ */
def withBodyAs[T](implicit m: Manifest[T]): Message = withBodyAs(m.erasure.asInstanceOf[Class[T]]) def withBodyAs[T](implicit m: Manifest[T], camelContext: CamelContext): CamelMessage = withBodyAs(m.erasure.asInstanceOf[Class[T]])
/** /**
* Creates a Message with current <code>body</code> converted to type <code>clazz</code>. * Creates a CamelMessage with current <code>body</code> converted to type <code>clazz</code>.
* <p> * <p>
* Java API * Java API
*/ */
def withBodyAs[T](clazz: Class[T]): Message = message.withBody(getBodyAs(clazz)) 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 * Returns the header with given <code>name</code> converted to type <code>T</code>. Throws
* <code>NoSuchElementException</code> if the header doesn't exist. * <code>NoSuchElementException</code> if the header doesn't exist.
*/ */
def headerAs[T](name: String)(implicit m: Manifest[T]): Option[T] = message.header(name).map(camelContext.getTypeConverter.mandatoryConvertTo[T](m.erasure.asInstanceOf[Class[T]], _)) 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> * Returns the header with given <code>name</code> converted to type as given by the <code>clazz</code>
@ -175,19 +173,19 @@ class RichMessage(message: Message, camelContext: CamelContext) {
* <p> * <p>
* Java API * Java API
*/ */
def getHeaderAs[T](name: String, clazz: Class[T]) = headerAs[T](name)(Manifest.classType(clazz)).get def getHeaderAs[T](name: String, clazz: Class[T], camelContext: CamelContext) = headerAs[T](name)(Manifest.classType(clazz), camelContext).get
} }
/** /**
* Companion object of Message class. * Companion object of CamelMessage class.
* *
* @author Martin Krasser * @author Martin Krasser
*/ */
object Message { object CamelMessage {
/** /**
* Message header to correlate request with response messages. Applications that send * 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 * 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 * so that it can be correlated with an asynchronous response. Messages send to Consumer
* actors have this header already set. * actors have this header already set.
@ -196,26 +194,26 @@ object Message {
/** /**
* Creates a canonical form of the given message <code>msg</code>. If <code>msg</code> of type * 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 * CamelMessage then <code>msg</code> is returned, otherwise <code>msg</code> is set as body of a
* newly created Message object. * newly created CamelMessage object.
*/ */
def canonicalize(msg: Any) = msg match { def canonicalize(msg: Any) = msg match {
case mobj: Message mobj case mobj: CamelMessage mobj
case body Message(body, Map.empty) case body CamelMessage(body, Map.empty)
} }
/** /**
* Creates a new Message object from the Camel message. * Creates a new CamelMessage object from the Camel message.
*/ */
def from(camelMessage: CamelMessage): Message = from(camelMessage, Map.empty) def from(camelMessage: JCamelMessage): CamelMessage = from(camelMessage, Map.empty)
/** /**
* Creates a new Message object from the Camel message. * Creates a new CamelMessage object from the Camel message.
* *
* @param headers additional headers to set on the created Message in addition to those * @param headers additional headers to set on the created CamelMessage in addition to those
* in the Camel message. * in the Camel message.
*/ */
def from(camelMessage: CamelMessage, headers: Map[String, Any]): Message = Message(camelMessage.getBody, headers ++ camelMessage.getHeaders) def from(camelMessage: JCamelMessage, headers: Map[String, Any]): CamelMessage = CamelMessage(camelMessage.getBody, headers ++ camelMessage.getHeaders)
} }

View file

@ -19,16 +19,22 @@ import akka.util.duration._
trait Consumer extends Actor with ConsumerConfig { trait Consumer extends Actor with ConsumerConfig {
def endpointUri: String def endpointUri: String
protected[this] implicit lazy val camel = CamelExtension(context.system)
protected[this] implicit def camel = CamelExtension(context.system)
protected[this] implicit def camelContext = camel.context
camel.registerConsumer(endpointUri, this, activationTimeout) camel.registerConsumer(endpointUri, this, activationTimeout)
} }
/**
* For internal use only.
*/
private[camel] object DefaultConsumerConfig extends ConsumerConfig
trait ConsumerConfig { trait ConsumerConfig {
//TODO: Explain the parameters better with some examples!
/** /**
* How long should the actor wait for activation before it fails. * How long the actor should wait for activation before it fails.
*/ */
def activationTimeout: Duration = 10 seconds def activationTimeout: Duration = 10 seconds
@ -50,18 +56,12 @@ trait ConsumerConfig {
/** /**
* 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.
*/ */
//TODO: write a test confirming onRouteDefinition gets called //FIXME: write a test confirming onRouteDefinition gets called
def onRouteDefinition(rd: RouteDefinition): ProcessorDefinition[_] = rd def onRouteDefinition(rd: RouteDefinition): ProcessorDefinition[_] = rd
/**
* For internal use only. Converts this ConsumerConfig to camel URI parameters
* @return
*/
private[camel] def toCamelParameters: String = "autoack=%s&replyTimeout=%s" format (autoack, DurationTypeConverter.toString(replyTimeout)) private[camel] def toCamelParameters: String = "autoack=%s&replyTimeout=%s" format (autoack, DurationTypeConverter.toString(replyTimeout))
} }
trait ManualAckConsumer extends Consumer {
override def autoack = false
}
trait ErrorPassing { self: Actor
final override def preRestart(reason: Throwable, message: Option[Any]) {
sender ! Failure(reason)
}
}

View file

@ -14,14 +14,14 @@ import org.apache.camel.{ Exchange, ExchangePattern, AsyncCallback }
* @author Martin Krasser * @author Martin Krasser
*/ */
trait ProducerSupport { this: Actor trait ProducerSupport { this: Actor
protected[this] implicit lazy val camel = CamelExtension(context.system) protected[this] implicit def camel = CamelExtension(context.system)
protected[this] lazy val (endpoint, processor) = camel.registerProducer(self, endpointUri) 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) private val headersToCopyDefault = Set(CamelMessage.MessageExchangeId)
/** /**
* If set to false (default), this producer expects a response message from the Camel endpoint. * If set to false (default), this producer expects a response message from the Camel endpoint.
@ -37,7 +37,7 @@ trait ProducerSupport { this: Actor ⇒
/** /**
* Returns the names of message headers to copy from a request message to a response message. * 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 * By default only the CamelMessage.MessageExchangeId is copied. Applications may override this to
* define an application-specific set of message headers to copy. * define an application-specific set of message headers to copy.
*/ */
def headersToCopy: Set[String] = headersToCopyDefault def headersToCopy: Set[String] = headersToCopyDefault
@ -52,7 +52,7 @@ trait ProducerSupport { this: Actor ⇒
* asynchronously. The original * asynchronously. The original
* sender and senderFuture are preserved. * sender and senderFuture are preserved.
* *
* @see Message#canonicalize(Any) * @see CamelMessage#canonicalize(Any)
* *
* @param msg message to produce * @param msg message to produce
* @param pattern exchange pattern * @param pattern exchange pattern
@ -60,7 +60,7 @@ trait ProducerSupport { this: Actor ⇒
protected def produce(msg: Any, pattern: ExchangePattern): Unit = { protected def produce(msg: Any, pattern: ExchangePattern): Unit = {
implicit def toExchangeAdapter(exchange: Exchange): CamelExchangeAdapter = new CamelExchangeAdapter(exchange) implicit def toExchangeAdapter(exchange: Exchange): CamelExchangeAdapter = new CamelExchangeAdapter(exchange)
val cmsg = Message.canonicalize(msg) val cmsg = CamelMessage.canonicalize(msg)
val exchange = endpoint.createExchange(pattern) val exchange = endpoint.createExchange(pattern)
exchange.setRequest(cmsg) exchange.setRequest(cmsg)
processor.process(exchange, new AsyncCallback { processor.process(exchange, new AsyncCallback {
@ -68,15 +68,10 @@ trait ProducerSupport { this: Actor ⇒
// Need copies of sender reference here since the callback could be done // Need copies of sender reference here since the callback could be done
// later by another thread. // later by another thread.
val originalSender = sender val originalSender = sender
// Ignoring doneSync, sending back async uniformly.
def done(doneSync: Boolean): Unit = { def done(doneSync: Boolean): Unit = producer.tell(
if (exchange.isFailed) { if (exchange.isFailed) FailureResult(exchange.toFailureMessage(cmsg.headers(headersToCopy)))
dispatch(FailureResult(exchange.toFailureMessage(cmsg.headers(headersToCopy)))) else MessageResult(exchange.toResponseMessage(cmsg.headers(headersToCopy))), originalSender)
} else {
dispatch(MessageResult(exchange.toResponseMessage(cmsg.headers(headersToCopy))))
}
}
private def dispatch(result: Any) { producer.tell(result, originalSender) }
}) })
} }
@ -135,12 +130,12 @@ trait Producer extends ProducerSupport { this: Actor ⇒
/** /**
* @author Martin Krasser * @author Martin Krasser
*/ */
private[camel] case class MessageResult(message: Message) private case class MessageResult(message: CamelMessage)
/** /**
* @author Martin Krasser * @author Martin Krasser
*/ */
private[camel] case class FailureResult(failure: Failure) private case class FailureResult(failure: Failure)
/** /**
* A one-way producer. * A one-way producer.

View file

@ -7,14 +7,24 @@ package akka.camel.internal
import akka.actor._ import akka.actor._
import collection.mutable.WeakHashMap import collection.mutable.WeakHashMap
class ActivationTracker extends Actor with ActorLogging { /**
* 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] val activations = new WeakHashMap[ActorRef, ActivationStateMachine]
/**
* A state machine that keeps track of the endpoint activation status of an actor.
*/
class ActivationStateMachine { class ActivationStateMachine {
type State = PartialFunction[ActivationMessage, Unit] type State = PartialFunction[ActivationMessage, Unit]
var receive: State = notActivated() var receive: State = notActivated()
/**
* Not activated state
* @return a partial function that handles messages in the 'not activated' state
*/
def notActivated(): State = { def notActivated(): State = {
var awaitingActivation = List[ActorRef]() var awaitingActivation = List[ActorRef]()
var awaitingDeActivation = List[ActorRef]() var awaitingDeActivation = List[ActorRef]()
@ -22,46 +32,59 @@ class ActivationTracker extends Actor with ActorLogging {
{ {
case AwaitActivation(ref) awaitingActivation ::= sender case AwaitActivation(ref) awaitingActivation ::= sender
case AwaitDeActivation(ref) awaitingDeActivation ::= sender case AwaitDeActivation(ref) awaitingDeActivation ::= sender
case msg @ EndpointActivated(ref)
case msg @ EndpointActivated(ref) {
awaitingActivation.foreach(_ ! msg) awaitingActivation.foreach(_ ! msg)
receive = activated(awaitingDeActivation) receive = activated(awaitingDeActivation)
} case EndpointFailedToActivate(ref, cause)
case EndpointFailedToActivate(ref, cause) {
awaitingActivation.foreach(_ ! EndpointFailedToActivate(ref, cause)) awaitingActivation.foreach(_ ! EndpointFailedToActivate(ref, cause))
receive = failedToActivate(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 = { def activated(currentAwaitingDeActivation: List[ActorRef]): State = {
var awaitingDeActivation = currentAwaitingDeActivation var awaitingDeActivation = currentAwaitingDeActivation
{ {
case AwaitActivation(ref) sender ! EndpointActivated(ref) case AwaitActivation(ref) sender ! EndpointActivated(ref)
case AwaitDeActivation(ref) awaitingDeActivation ::= sender case AwaitDeActivation(ref) awaitingDeActivation ::= sender
case msg @ EndpointDeActivated(ref) { case msg @ EndpointDeActivated(ref)
awaitingDeActivation foreach (_ ! msg) awaitingDeActivation foreach (_ ! msg)
receive = deactivated receive = deactivated
} case msg @ EndpointFailedToDeActivate(ref, cause)
case msg @ EndpointFailedToDeActivate(ref, cause) {
awaitingDeActivation foreach (_ ! msg) awaitingDeActivation foreach (_ ! msg)
receive = failedToDeActivate(cause) receive = failedToDeActivate(cause)
}
} }
} }
/**
* De-activated state
* @return a partial function that handles messages in the 'de-activated' state
*/
def deactivated: State = { def deactivated: State = {
case AwaitActivation(ref) sender ! EndpointActivated(ref) case AwaitActivation(ref) sender ! EndpointActivated(ref)
case AwaitDeActivation(ref) sender ! EndpointDeActivated(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 = { def failedToActivate(cause: Throwable): State = {
case AwaitActivation(ref) sender ! EndpointFailedToActivate(ref, cause) case AwaitActivation(ref) sender ! EndpointFailedToActivate(ref, cause)
case AwaitDeActivation(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 = { def failedToDeActivate(cause: Throwable): State = {
case AwaitActivation(ref) sender ! EndpointActivated(ref) case AwaitActivation(ref) sender ! EndpointActivated(ref)
case AwaitDeActivation(ref) sender ! EndpointFailedToDeActivate(ref, cause) case AwaitDeActivation(ref) sender ! EndpointFailedToDeActivate(ref, cause)
@ -69,15 +92,21 @@ class ActivationTracker extends Actor with ActorLogging {
} }
/**
* Subscribes self to messages of type <code>ActivationMessage</code>
*/
override def preStart() { override def preStart() {
context.system.eventStream.subscribe(self, classOf[ActivationMessage]) context.system.eventStream.subscribe(self, classOf[ActivationMessage])
} }
/**
*
* @return
*/
override def receive = { override def receive = {
case msg @ ActivationMessage(ref) { case msg @ ActivationMessage(ref)
val state = activations.getOrElseUpdate(ref, new ActivationStateMachine) val state = activations.getOrElseUpdate(ref, new ActivationStateMachine)
(state.receive orElse logStateWarning(ref))(msg) (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) } private[this] def logStateWarning(actorRef: ActorRef): Receive = { case msg log.warning("Message [{}] not expected in current state of actor [{}]", msg, actorRef) }

View file

@ -7,31 +7,31 @@ import scala.collection.JavaConversions._
import org.apache.camel.util.ExchangeHelper import org.apache.camel.util.ExchangeHelper
import akka.japi.{ Function JFunction } import akka.japi.{ Function JFunction }
import org.apache.camel.{ Exchange, Message CamelMessage } import org.apache.camel.{ Exchange, Message JCamelMessage }
import akka.camel.{ Failure, Message } import akka.camel.{ Failure, CamelMessage }
/** /**
* Adapter for converting an org.apache.camel.Exchange to and from Message and Failure objects. * For internal use only.
* Adapter for converting an org.apache.camel.Exchange to and from CamelMessage and Failure objects.
* *
* @author Martin Krasser * @author Martin Krasser
*/ */
//TODO: rething/rewrite this
private[camel] class CamelExchangeAdapter(exchange: Exchange) { private[camel] class CamelExchangeAdapter(exchange: Exchange) {
def getExchangeId = exchange.getExchangeId def getExchangeId = exchange.getExchangeId
def isOutCapable = exchange.getPattern.isOutCapable def isOutCapable = exchange.getPattern.isOutCapable
/** /**
* Sets Exchange.getIn from the given Message object. * Sets Exchange.getIn from the given CamelMessage object.
*/ */
def setRequest(msg: Message) { msg.copyContentTo(request) } def setRequest(msg: CamelMessage) { msg.copyContentTo(request) }
/** /**
* Depending on the exchange pattern, sets Exchange.getIn or Exchange.getOut from the given * 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 * CamelMessage object. If the exchange is out-capable then the Exchange.getOut is set, otherwise
* Exchange.getIn. * Exchange.getIn.
*/ */
def setResponse(msg: Message) { msg.copyContentTo(response) } def setResponse(msg: CamelMessage) { msg.copyContentTo(response) }
/** /**
* Sets Exchange.getException from the given Failure message. Headers of the Failure message * Sets Exchange.getException from the given Failure message. Headers of the Failure message
@ -40,15 +40,15 @@ private[camel] class CamelExchangeAdapter(exchange: Exchange) {
def setFailure(msg: Failure) { exchange.setException(msg.cause) } def setFailure(msg: Failure) { exchange.setException(msg.cause) }
/** /**
* Creates a Message object from Exchange.getIn. * Creates a CamelMessage object from Exchange.getIn.
*/ */
def toRequestMessage: Message = toRequestMessage(Map.empty) def toRequestMessage: CamelMessage = toRequestMessage(Map.empty)
/** /**
* Depending on the exchange pattern, creates a Message object from Exchange.getIn or Exchange.getOut. * 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. * If the exchange is out-capable then the Exchange.getOut is set, otherwise Exchange.getIn.
*/ */
def toResponseMessage: Message = toResponseMessage(Map.empty) def toResponseMessage: CamelMessage = toResponseMessage(Map.empty)
/** /**
* Creates a Failure object from the adapted Exchange. * Creates a Failure object from the adapted Exchange.
@ -58,26 +58,26 @@ private[camel] class CamelExchangeAdapter(exchange: Exchange) {
def toFailureMessage: Failure = toFailureMessage(Map.empty) def toFailureMessage: Failure = toFailureMessage(Map.empty)
/** /**
* Creates a Message object from Exchange.getIn. * Creates a CamelMessage object from Exchange.getIn.
* *
* @param headers additional headers to set on the created Message in addition to those * @param headers additional headers to set on the created CamelMessage in addition to those
* in the Camel message. * in the Camel message.
*/ */
def toRequestMessage(headers: Map[String, Any]): Message = Message.from(request, headers) def toRequestMessage(headers: Map[String, Any]): CamelMessage = CamelMessage.from(request, headers)
/** /**
* Depending on the exchange pattern, creates a Message object from Exchange.getIn or Exchange.getOut. * 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. * 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 * @param headers additional headers to set on the created CamelMessage in addition to those
* in the Camel message. * in the Camel message.
*/ */
def toResponseMessage(headers: Map[String, Any]): Message = Message.from(response, headers) def toResponseMessage(headers: Map[String, Any]): CamelMessage = CamelMessage.from(response, headers)
/** /**
* Creates a Failure object from the adapted Exchange. * Creates a Failure object from the adapted Exchange.
* *
* @param headers additional headers to set on the created Message in addition to those * @param headers additional headers to set on the created CamelMessage in addition to those
* in the Camel message. * in the Camel message.
* *
* @see Failure * @see Failure
@ -87,6 +87,6 @@ private[camel] class CamelExchangeAdapter(exchange: Exchange) {
private def request = exchange.getIn private def request = exchange.getIn
private def response: CamelMessage = ExchangeHelper.getResultMessage(exchange) private def response: JCamelMessage = ExchangeHelper.getResultMessage(exchange)
} }

View file

@ -17,6 +17,7 @@ import org.apache.camel.CamelContext
import akka.util.Duration import akka.util.Duration
/** /**
* For internal use only.
* Manages consumer registration. Consumers call registerConsumer method to register themselves when they get created. * Manages consumer registration. Consumers call registerConsumer method to register themselves when they get created.
* ActorEndpoint uses it to lookup an actor by its path. * ActorEndpoint uses it to lookup an actor by its path.
*/ */
@ -24,8 +25,17 @@ private[camel] trait ConsumerRegistry { this: Activation ⇒
def system: ActorSystem def system: ActorSystem
def context: CamelContext def context: CamelContext
/**
* For internal use only.
*/
private[this] lazy val idempotentRegistry = system.actorOf(Props(new IdempotentCamelConsumerRegistry(context))) 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) = { private[camel] def registerConsumer(endpointUri: String, consumer: Consumer, activationTimeout: Duration) = {
idempotentRegistry ! RegisterConsumer(endpointUri, consumer.self, consumer) idempotentRegistry ! RegisterConsumer(endpointUri, consumer.self, consumer)
awaitActivation(consumer.self, activationTimeout) awaitActivation(consumer.self, activationTimeout)
@ -33,6 +43,7 @@ private[camel] trait ConsumerRegistry { this: Activation ⇒
} }
/** /**
* For internal use only.
* Guarantees idempotent registration of camel consumer endpoints. * Guarantees idempotent registration of camel consumer endpoints.
* *
* Once registered the consumer is watched and unregistered upon termination. * Once registered the consumer is watched and unregistered upon termination.
@ -48,46 +59,42 @@ private[camel] class IdempotentCamelConsumerRegistry(camelContext: CamelContext)
val registrator = context.actorOf(Props(new CamelConsumerRegistrator)) val registrator = context.actorOf(Props(new CamelConsumerRegistrator))
def receive = { def receive = {
case msg @ RegisterConsumer(_, consumer, _) unless(isAlreadyActivated(consumer)) { case msg @ RegisterConsumer(_, consumer, _)
activated.add(consumer) if (!isAlreadyActivated(consumer)) {
registrator ! msg activated.add(consumer)
} registrator ! msg
case msg @ EndpointActivated(consumer) { }
case msg @ EndpointActivated(consumer)
context.watch(consumer) context.watch(consumer)
context.system.eventStream.publish(msg) context.system.eventStream.publish(msg)
} case msg @ EndpointFailedToActivate(consumer, _)
case msg @ EndpointFailedToActivate(consumer, _) {
activated.remove(consumer) activated.remove(consumer)
context.system.eventStream.publish(msg) context.system.eventStream.publish(msg)
} case Terminated(ref)
case Terminated(ref) {
activated.remove(ref) activated.remove(ref)
registrator ! UnregisterConsumer(ref) registrator ! UnregisterConsumer(ref)
} case msg @ EndpointFailedToDeActivate(ref, cause) context.system.eventStream.publish(msg)
case msg @ EndpointDeActivated(ref) { context.system.eventStream.publish(msg) } case msg: EndpointDeActivated context.system.eventStream.publish(msg)
case msg @ EndpointFailedToDeActivate(ref, cause) { context.system.eventStream.publish(msg) }
} }
def unless[A](condition: Boolean)(block: A) = if (!condition) block
def isAlreadyActivated(ref: ActorRef): Boolean = activated.contains(ref) def isAlreadyActivated(ref: ActorRef): Boolean = activated.contains(ref)
class CamelConsumerRegistrator extends Actor with ActorLogging { class CamelConsumerRegistrator extends Actor with ActorLogging {
def receive = { def receive = {
case RegisterConsumer(endpointUri, consumer, consumerConfig) { case RegisterConsumer(endpointUri, consumer, consumerConfig)
camelContext.addRoutes(new ConsumerActorRouteBuilder(endpointUri, consumer, consumerConfig)) camelContext.addRoutes(new ConsumerActorRouteBuilder(endpointUri, consumer, consumerConfig))
context.sender ! EndpointActivated(consumer) context.sender ! EndpointActivated(consumer)
log.debug("Published actor [{}] at endpoint [{}]", consumerConfig, endpointUri) log.debug("Published actor [{}] at endpoint [{}]", consumerConfig, endpointUri)
} case UnregisterConsumer(consumer)
case UnregisterConsumer(consumer) {
camelContext.stopRoute(consumer.path.toString) camelContext.stopRoute(consumer.path.toString)
context.sender ! EndpointDeActivated(consumer) context.sender ! EndpointDeActivated(consumer)
log.debug("Unpublished actor [{}] from endpoint [{}]", consumer, consumer.path) log.debug("Unpublished actor [{}] from endpoint [{}]", consumer, consumer.path)
}
} }
override def preRestart(reason: Throwable, message: Option[Any]) { override def preRestart(reason: Throwable, message: Option[Any]) {
//FIXME check logic
super.preStart()
message match { message match {
case Some(RegisterConsumer(_, consumer, _)) sender ! EndpointFailedToActivate(consumer, reason) case Some(RegisterConsumer(_, consumer, _)) sender ! EndpointFailedToActivate(consumer, reason)
case Some(UnregisterConsumer(consumer)) sender ! EndpointFailedToDeActivate(consumer, reason) case Some(UnregisterConsumer(consumer)) sender ! EndpointFailedToDeActivate(consumer, reason)
@ -97,9 +104,16 @@ private[camel] class IdempotentCamelConsumerRegistry(camelContext: CamelContext)
} }
} }
/**
* 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) 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. * 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 endpointUri endpoint URI of the consumer actor or typed actor method.
@ -115,7 +129,7 @@ private[camel] class ConsumerActorRouteBuilder(endpointUri: String, consumer: Ac
val scheme = endpointUri take endpointUri.indexOf(":") // e.g. "http" from "http://whatever/..." val scheme = endpointUri take endpointUri.indexOf(":") // e.g. "http" from "http://whatever/..."
val route = from(endpointUri).routeId(consumer.path.toString) val route = from(endpointUri).routeId(consumer.path.toString)
val converted = Conversions.apply(scheme, route) val converted = Conversions(scheme, route)
val userCustomized = applyUserRouteCustomization(converted) val userCustomized = applyUserRouteCustomization(converted)
userCustomized.to(targetActorUri) userCustomized.to(targetActorUri)
} }
@ -138,18 +152,39 @@ private[camel] class ConsumerActorRouteBuilder(endpointUri: String, consumer: Ac
/** /**
* Super class of all activation messages. * Super class of all activation messages.
*/ */
private[camel] abstract class ActivationMessage(val actor: ActorRef) abstract class ActivationMessage(val actor: ActorRef)
/**
* For internal use only. companion object of <code>ActivationMessage</code>
*
*/
private[camel] object ActivationMessage { private[camel] object ActivationMessage {
def unapply(msg: ActivationMessage): Option[ActorRef] = Some(msg.actor) def unapply(msg: ActivationMessage): Option[ActorRef] = Some(msg.actor)
} }
/** /**
* For internal use only.
* Event message indicating that a single endpoint has been activated. * Event message indicating that a single endpoint has been activated.
*/ */
private[camel] case class EndpointActivated(actorRef: ActorRef) extends ActivationMessage(actorRef) sealed case class EndpointActivated(actorRef: ActorRef) extends ActivationMessage(actorRef)
private[camel] case class EndpointFailedToActivate(actorRef: ActorRef, cause: Throwable) 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)
private[camel] case class EndpointDeActivated(actorRef: ActorRef) extends ActivationMessage(actorRef) /**
* For internal use only.
* @param actorRef the endpoint that was de-activated
*/
sealed case class EndpointDeActivated(actorRef: ActorRef) extends ActivationMessage(actorRef)
private[camel] case class EndpointFailedToDeActivate(actorRef: ActorRef, cause: Throwable) 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

@ -4,12 +4,13 @@ import akka.actor.ActorSystem
import component.{ DurationTypeConverter, ActorComponent } import component.{ DurationTypeConverter, ActorComponent }
import org.apache.camel.CamelContext import org.apache.camel.CamelContext
import org.apache.camel.impl.DefaultCamelContext import org.apache.camel.impl.DefaultCamelContext
import akka.util.Duration
import scala.Predef._ import scala.Predef._
import akka.event.Logging import akka.event.Logging
import akka.camel.Camel import akka.camel.Camel
import akka.util.{ NonFatal, Duration }
/** /**
* For internal use only.
* Creates an instance of the Camel subsystem. * Creates an instance of the Camel subsystem.
* *
* @param system is used to create internal actors needed by camel instance. * @param system is used to create internal actors needed by camel instance.
@ -18,6 +19,9 @@ import akka.camel.Camel
* Also by not creating extra internal actor system we are conserving resources. * Also by not creating extra internal actor system we are conserving resources.
*/ */
private[camel] class DefaultCamel(val system: ActorSystem) extends Camel { private[camel] class DefaultCamel(val system: ActorSystem) extends Camel {
/**
* For internal use only.
*/
private[camel] implicit val log = Logging(system, "Camel") private[camel] implicit val log = Logging(system, "Camel")
lazy val context: CamelContext = { lazy val context: CamelContext = {
@ -38,7 +42,7 @@ private[camel] class DefaultCamel(val system: ActorSystem) extends Camel {
*/ */
def start = { def start = {
context.start() context.start()
Try(template.start()) otherwise context.stop() try template.start() catch { case NonFatal(e) context.stop(); throw e }
log.debug("Started CamelContext[{}] for ActorSystem[{}]", context.getName, system.name) log.debug("Started CamelContext[{}] for ActorSystem[{}]", context.getName, system.name)
this this
} }
@ -51,7 +55,9 @@ private[camel] class DefaultCamel(val system: ActorSystem) extends Camel {
* @see akka.camel.DefaultCamel#start() * @see akka.camel.DefaultCamel#start()
*/ */
def shutdown() { def shutdown() {
try context.stop() finally Try.safe(template.stop()) 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) log.debug("Stopped CamelContext[{}] for ActorSystem[{}]", context.getName, system.name)
} }
} }

View file

@ -5,26 +5,24 @@ import org.apache.camel.processor.SendProcessor
import akka.actor.{ Props, ActorRef, Terminated, Actor } import akka.actor.{ Props, ActorRef, Terminated, Actor }
import org.apache.camel.Endpoint import org.apache.camel.Endpoint
import akka.camel.Camel import akka.camel.Camel
import akka.util.NonFatal
/** /**
* Watches the end of life of <code>Producer</code>s. * 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>, * Removes a <code>Producer</code> from the <code>ProducerRegistry</code> when it is <code>Terminated</code>,
* which in turn stops the <code>SendProcessor</code>. * which in turn stops the <code>SendProcessor</code>.
*/ */
private[camel] class ProducerWatcher(registry: ProducerRegistry) extends Actor { private class ProducerWatcher(registry: ProducerRegistry) extends Actor {
override def receive = { override def receive = {
case RegisterProducer(actorRef) { case RegisterProducer(actorRef) context.watch(actorRef)
context.watch(actorRef) case Terminated(actorRef) registry.unregisterProducer(actorRef)
}
case Terminated(actorRef) {
registry.unregisterProducer(actorRef)
}
} }
} }
private[camel] case class RegisterProducer(actorRef: ActorRef) private case class RegisterProducer(actorRef: ActorRef)
/** /**
* For internal use only.
* Manages the Camel objects for <code>Producer</code>s. * Manages the Camel objects for <code>Producer</code>s.
* Every <code>Producer</code> needs an <code>Endpoint</code> and a <code>SendProcessor</code> * Every <code>Producer</code> needs an <code>Endpoint</code> and a <code>SendProcessor</code>
* to produce messages over an <code>Exchange</code>. * to produce messages over an <code>Exchange</code>.
@ -39,6 +37,7 @@ private[camel] trait ProducerRegistry {
} }
/** /**
* For internal use only.
* Unregisters <code>Endpoint</code> and <code>SendProcessor</code> and stops the SendProcessor * Unregisters <code>Endpoint</code> and <code>SendProcessor</code> and stops the SendProcessor
*/ */
private[camel] def unregisterProducer(actorRef: ActorRef): Unit = { private[camel] def unregisterProducer(actorRef: ActorRef): Unit = {
@ -49,12 +48,13 @@ private[camel] trait ProducerRegistry {
processor.stop() processor.stop()
system.eventStream.publish(EndpointDeActivated(actorRef)) system.eventStream.publish(EndpointDeActivated(actorRef))
} catch { } catch {
case e system.eventStream.publish(EndpointFailedToDeActivate(actorRef, e)) 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. * 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 actorRef the actorRef of the <code>Producer</code> actor.
* @param endpointUri the endpoint Uri of the producer * @param endpointUri the endpoint Uri of the producer
@ -65,17 +65,16 @@ private[camel] trait ProducerRegistry {
val endpoint = context.getEndpoint(endpointUri) val endpoint = context.getEndpoint(endpointUri)
val processor = new SendProcessor(endpoint) val processor = new SendProcessor(endpoint)
val prev = camelObjects.putIfAbsent(actorRef, (endpoint, processor)) camelObjects.putIfAbsent(actorRef, (endpoint, processor)) match {
if (prev != null) { case null
prev processor.start()
} else { registerWatch(actorRef)
processor.start() system.eventStream.publish(EndpointActivated(actorRef))
system.eventStream.publish(EndpointActivated(actorRef)) (endpoint, processor)
registerWatch(actorRef) case prev prev
(endpoint, processor)
} }
} catch { } catch {
case e { case NonFatal(e) {
system.eventStream.publish(EndpointFailedToActivate(actorRef, e)) system.eventStream.publish(EndpointFailedToActivate(actorRef, e))
// can't return null to the producer actor, so blow up actor in initialization. // can't return null to the producer actor, so blow up actor in initialization.
throw e throw e

View file

@ -1,53 +0,0 @@
package akka.camel.internal
/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
import akka.event.LoggingAdapter
private[camel] object Try {
/**
* Tries to execute body.
*
* Example below tries to start template and if it's unsuccessful it stops context in a safe way, by logging exceptions and then it rethrows exception thrown by template.start()
* <pre> Try(template.start()) otherwise context.stop() </pre>
*
* @param body block of code to execute.
* @return Ok, if no exception is thrown by body.
* @return Failed, if exception was thrown by body.
*
*/
def apply(body: Unit): Result =
try {
body; Ok
} catch {
case e Failed(e)
}
sealed trait Result {
def otherwise(onError: Unit)(implicit log: LoggingAdapter): Unit = ()
}
private[this] case object Ok extends Result
private[this] case class Failed(e: Throwable) extends Result {
override def otherwise(onError: Unit)(implicit log: LoggingAdapter) = {
safe(onError)
throw e
}
}
/**
* Executes the block and logs the exception, if it's thrown by the block, and swallows the exception.
*/
@inline def safe(block: Unit)(implicit log: LoggingAdapter) {
try {
block
} catch {
case e log.warning("Safe operation failed. Swallowing exception [{}]", e)
}
}
}

View file

@ -13,13 +13,14 @@ import akka.actor._
import akka.pattern._ import akka.pattern._
import scala.reflect.BeanProperty import scala.reflect.BeanProperty
import akka.util.{ Duration, Timeout }
import akka.util.duration._ import akka.util.duration._
import java.util.concurrent.{ TimeoutException, CountDownLatch } import java.util.concurrent.{ TimeoutException, CountDownLatch }
import akka.camel.{ ConsumerConfig, Camel, Ack, Failure CamelFailure, Message }
import akka.camel.internal.CamelExchangeAdapter 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. * Camel component for sending messages to and receiving replies from (untyped) actors.
* *
* @see akka.camel.component.ActorEndpoint * @see akka.camel.component.ActorEndpoint
@ -27,7 +28,7 @@ import akka.camel.internal.CamelExchangeAdapter
* *
* @author Martin Krasser * @author Martin Krasser
*/ */
class ActorComponent(camel: Camel) extends DefaultComponent { private[camel] class ActorComponent(camel: Camel) extends DefaultComponent {
def createEndpoint(uri: String, remaining: String, parameters: JMap[String, Object]): ActorEndpoint = { def createEndpoint(uri: String, remaining: String, parameters: JMap[String, Object]): ActorEndpoint = {
val path = ActorEndpointPath.fromCamelPath(remaining) val path = ActorEndpointPath.fromCamelPath(remaining)
new ActorEndpoint(uri, this, path, camel) new ActorEndpoint(uri, this, path, camel)
@ -35,32 +36,31 @@ class ActorComponent(camel: Camel) extends DefaultComponent {
} }
/** /**
* TODO fix the doc to be consistent with implementation * For internal use only.
* Camel endpoint for sending messages to and receiving replies from (untyped) actors. Actors * The ActorEndpoint is a Camel Endpoint that is used to receive messages from Camel through the ActorComponent
* are referenced using <code>actor</code> endpoint URIs of the following format: * Actors are referenced using <code>actor</code> endpoint URIs of the following format:
* <code>actor:<actor-id></code>, * <code>actor://path:[actorPath]?[options]%s</code>,
* <code>actor:id:[<actor-id>]</code> and * where <code>[actorPath]</code> refers to the Actor Path to the Actor.
* <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.ActorComponent
* @see akka.camel.component.ActorProducer * @see akka.camel.component.ActorProducer
* *
* @author Martin Krasser * @author Martin Krasser
*/ */
class ActorEndpoint(uri: String, private[camel] class ActorEndpoint(uri: String,
comp: ActorComponent, comp: ActorComponent,
val path: ActorEndpointPath, val path: ActorEndpointPath,
camel: Camel) extends DefaultEndpoint(uri, comp) with ActorEndpointConfig { camel: Camel) extends DefaultEndpoint(uri, comp) with ActorEndpointConfig {
/** /**
* @throws UnsupportedOperationException *
* 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 = def createConsumer(processor: Processor): org.apache.camel.Consumer =
throw new UnsupportedOperationException("actor consumer not supported yet") throw new UnsupportedOperationException("actor consumer not supported yet")
@ -76,7 +76,7 @@ class ActorEndpoint(uri: String,
def isSingleton: Boolean = true def isSingleton: Boolean = true
} }
trait ActorEndpointConfig { private[camel] trait ActorEndpointConfig {
def path: ActorEndpointPath def path: ActorEndpointPath
@BeanProperty var replyTimeout: Duration = 1 minute @BeanProperty var replyTimeout: Duration = 1 minute
@ -91,43 +91,54 @@ trait ActorEndpointConfig {
@BeanProperty var autoack: Boolean = true @BeanProperty var autoack: Boolean = true
} }
//FIXME: rewrite this doc
/** /**
* Sends the in-message of an exchange to an (untyped) actor, identified by an * Sends the in-message of an exchange to an untyped actor, identified by an [[akka.camel.internal.component.ActorEndPoint]]
* 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.ActorComponent
* @see akka.camel.component.ActorEndpoint * @see akka.camel.component.ActorEndpoint
* *
* @author Martin Krasser * @author Martin Krasser
*/ */
class ActorProducer(val ep: ActorEndpoint, camel: Camel) extends DefaultProducer(ep) with AsyncProcessor { private[camel] class ActorProducer(val endpoint: ActorEndpoint, camel: Camel) extends DefaultProducer(endpoint) with AsyncProcessor {
def process(exchange: Exchange) { new ConsumerAsyncProcessor(ep, camel).process(new CamelExchangeAdapter(exchange)) } /**
def process(exchange: Exchange, callback: AsyncCallback) = new ConsumerAsyncProcessor(ep, camel).process(new CamelExchangeAdapter(exchange), callback) * 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)) }
class ConsumerAsyncProcessor(config: ActorEndpointConfig, camel: Camel) { /**
* 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) }
def process(exchange: CamelExchangeAdapter) { /**
* 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) val isDone = new CountDownLatch(1)
process(exchange, new AsyncCallback { def done(doneSync: Boolean) { isDone.countDown() } }) 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. isDone.await() // this should never wait forever as the process(exchange, callback) method guarantees that.
} }
def process(exchange: CamelExchangeAdapter, callback: AsyncCallback): Boolean = { /**
* 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 = {
// this notify methods are just a syntax sugar // these notify methods are just a syntax sugar
def notifyDoneSynchronously[A](a: A = null) = callback.done(true) def notifyDoneSynchronously[A](a: A = null) = callback.done(true)
def notifyDoneAsynchronously[A](a: A = null) = callback.done(false) def notifyDoneAsynchronously[A](a: A = null) = callback.done(false)
@ -136,7 +147,7 @@ class ConsumerAsyncProcessor(config: ActorEndpointConfig, camel: Camel) {
if (exchange.isOutCapable) { //InOut if (exchange.isOutCapable) { //InOut
sendAsync(message, onComplete = forwardResponseTo(exchange) andThen notifyDoneAsynchronously) sendAsync(message, onComplete = forwardResponseTo(exchange) andThen notifyDoneAsynchronously)
} else { // inOnly } else { // inOnly
if (config.autoack) { //autoAck if (endpoint.autoack) { //autoAck
fireAndForget(message, exchange) fireAndForget(message, exchange)
notifyDoneSynchronously() notifyDoneSynchronously()
true // done sync true // done sync
@ -146,46 +157,45 @@ class ConsumerAsyncProcessor(config: ActorEndpointConfig, camel: Camel) {
} }
} }
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 sendAsync(message: Message, onComplete: PartialFunction[Either[Throwable, Any], Unit]): Boolean = { 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 { try {
val actor = actorFor(config.path) val actor = actorFor(endpoint.path)
val future = actor.ask(message)(new Timeout(config.replyTimeout)) val future = actor.ask(message)(new Timeout(endpoint.replyTimeout))
future.onComplete(onComplete) future.onComplete(onComplete)
} catch { } catch {
case e onComplete(Left(e)) case NonFatal(e) onComplete(Left(e))
} }
false // Done async false // Done async
} }
private def fireAndForget(message: Message, exchange: CamelExchangeAdapter) { private def fireAndForget(message: CamelMessage, exchange: CamelExchangeAdapter) {
try { try {
actorFor(config.path) ! message actorFor(endpoint.path) ! message
} catch { } catch {
case e exchange.setFailure(new CamelFailure(e)) case e exchange.setFailure(new CamelFailure(e))
} }
} }
private[this] def forwardResponseTo(exchange: CamelExchangeAdapter): PartialFunction[Either[Throwable, Any], Unit] = {
case Right(failure: CamelFailure) exchange.setFailure(failure);
case Right(msg) exchange.setResponse(Message.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 (config.path, config.replyTimeout, config))))
case Left(throwable) exchange.setFailure(CamelFailure(throwable))
}
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, config.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 (config.path, config.replyTimeout, config))))
case Left(throwable) exchange.setFailure(CamelFailure(throwable))
}
private[this] def actorFor(path: ActorEndpointPath): ActorRef = private[this] def actorFor(path: ActorEndpointPath): ActorRef =
path.findActorIn(camel.system) getOrElse (throw new ActorNotRegisteredException(path.actorPath)) path.findActorIn(camel.system) getOrElse (throw new ActorNotRegisteredException(path.actorPath))
private[this] def messageFor(exchange: CamelExchangeAdapter) = private[this] def messageFor(exchange: CamelExchangeAdapter) =
exchange.toRequestMessage(Map(Message.MessageExchangeId -> exchange.getExchangeId)) exchange.toRequestMessage(Map(CamelMessage.MessageExchangeId -> exchange.getExchangeId))
} }
@ -199,25 +209,34 @@ class ActorNotRegisteredException(uri: String) extends RuntimeException {
override def getMessage = "Actor [%s] doesn't exist" format uri override def getMessage = "Actor [%s] doesn't exist" format uri
} }
/**
* For internal use only.
*/
private[camel] object DurationTypeConverter extends CamelTypeConverter { private[camel] object DurationTypeConverter extends CamelTypeConverter {
def convertTo[T](`type`: Class[T], value: AnyRef) = { def convertTo[T](`type`: Class[T], value: AnyRef) = {
require(value.toString.endsWith(" nanos")) Duration(value.toString).asInstanceOf[T]
Duration.fromNanos(value.toString.dropRight(6).toLong).asInstanceOf[T]
} }
def toString(duration: Duration) = duration.toNanos + " nanos" def toString(duration: Duration) = duration.toNanos + " nanos"
} }
/**
* For internal use only.
*/
private[camel] abstract class CamelTypeConverter extends TypeConverter { private[camel] abstract class CamelTypeConverter extends TypeConverter {
def convertTo[T](`type`: Class[T], exchange: Exchange, value: AnyRef) = convertTo(`type`, value) 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], value: AnyRef) = convertTo(`type`, value)
def mandatoryConvertTo[T](`type`: Class[T], exchange: Exchange, 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) { private[camel] case class ActorEndpointPath private (actorPath: String) {
require(actorPath != null) require(actorPath != null)
require(actorPath.length() > 0) require(actorPath.length() > 0)
def toCamelPath(config: ConsumerConfig = new ConsumerConfig {}): String = "actor://path:%s?%s" format (actorPath, config.toCamelParameters) def toCamelPath(config: ConsumerConfig = DefaultConsumerConfig): String = "actor://path:%s?%s" format (actorPath, config.toCamelParameters)
def findActorIn(system: ActorSystem): Option[ActorRef] = { def findActorIn(system: ActorSystem): Option[ActorRef] = {
val ref = system.actorFor(actorPath) val ref = system.actorFor(actorPath)
@ -226,6 +245,9 @@ private[camel] case class ActorEndpointPath private (actorPath: String) {
} }
/**
* For internal use only. Companion of <code>ActorEndpointPath</code>
*/
private[camel] object ActorEndpointPath { private[camel] object ActorEndpointPath {
def apply(actorRef: ActorRef) = new ActorEndpointPath(actorRef.path.toString) def apply(actorRef: ActorRef) = new ActorEndpointPath(actorRef.path.toString)

View file

@ -6,6 +6,7 @@ package akka.camel.javaapi
import akka.actor.UntypedActor import akka.actor.UntypedActor
import akka.camel._ import akka.camel._
import org.apache.camel.{ ProducerTemplate, CamelContext }
/** /**
* Java-friendly Consumer. * Java-friendly Consumer.
@ -23,11 +24,22 @@ trait UntypedConsumer extends Consumer { self: UntypedActor ⇒
*/ */
def getEndpointUri(): String def getEndpointUri(): String
def rich(message: Message): RichMessage = message
} }
/** /**
* Subclass this abstract class to create an MDB-style untyped consumer actor. This * Subclass this abstract class to create an MDB-style untyped consumer actor. This
* class is meant to be used from Java. * class is meant to be used from Java.
*/ */
abstract class UntypedConsumerActor extends UntypedActor with UntypedConsumer 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

@ -19,7 +19,7 @@ abstract class UntypedProducerActor extends UntypedActor with ProducerSupport {
* message is passed as argument. By default, this method simply returns the argument but may be overridden * message is passed as argument. By default, this method simply returns the argument but may be overridden
* by subclasses. * by subclasses.
*/ */
def onReceiveBeforeProduce(message: Any): Any = super.receiveBeforeProduce(message) def onReceiveBeforeProduce(message: AnyRef): AnyRef = message
/** /**
* Called after a response was received from the endpoint specified by <code>endpointUri</code>. The * Called after a response was received from the endpoint specified by <code>endpointUri</code>. The
@ -27,14 +27,14 @@ abstract class UntypedProducerActor extends UntypedActor with ProducerSupport {
* if <code>oneway</code> is <code>false</code>. If <code>oneway</code> is <code>true</code>, nothing is * 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). * done. This method may be overridden by subclasses (e.g. to forward responses to another actor).
*/ */
def onReceiveAfterProduce(message: Any): Unit = super.receiveAfterProduce(message) def onReceiveAfterProduce(message: AnyRef): Unit = super.receiveAfterProduce(message)
final override def receiveBeforeProduce = { final override def receiveBeforeProduce = {
case msg onReceiveBeforeProduce(msg) case msg: AnyRef onReceiveBeforeProduce(msg)
} }
final override def receiveAfterProduce = { final override def receiveAfterProduce = {
case msg onReceiveAfterProduce(msg) case msg: AnyRef onReceiveAfterProduce(msg)
} }
final override def endpointUri = getEndpointUri final override def endpointUri = getEndpointUri
@ -60,13 +60,6 @@ abstract class UntypedProducerActor extends UntypedActor with ProducerSupport {
*/ */
def isOneway() = super.oneway def isOneway() = super.oneway
/**
* Creates a <code>RichMessage</code> out of a message. The <code>RichMessage</code> has convenience methods for accessing body and headers
* @param message the message
* @return the <code>RichMessage</code>
*/
def rich(message: Message): RichMessage = message
/** /**
* Returns the <code>CamelContext</code>. * Returns the <code>CamelContext</code>.
*/ */

View file

@ -20,7 +20,7 @@ class SysOutConsumer extends Consumer {
def endpointUri = "file://data/input/CamelConsumer" def endpointUri = "file://data/input/CamelConsumer"
protected def receive = { protected def receive = {
case msg: Message { case msg: CamelMessage {
printf("Received '%s'\n", msg.bodyAs[String]) printf("Received '%s'\n", msg.bodyAs[String])
} }
} }
@ -34,8 +34,9 @@ class TroubleMaker extends Consumer {
} }
class SysOutActor(implicit camel: Camel) extends Actor { class SysOutActor(implicit camel: Camel) extends Actor {
implicit val camelContext = camel.context
protected def receive = { protected def receive = {
case msg: Message { case msg: CamelMessage {
printf("Received '%s'\n", msg.bodyAs[String]) printf("Received '%s'\n", msg.bodyAs[String])
} }
} }

View file

@ -8,5 +8,4 @@ import org.apache.camel.model.ProcessorDefinition
package object camel { package object camel {
implicit def toActorRouteDefinition(definition: ProcessorDefinition[_]) = new ActorRouteDefinition(definition) implicit def toActorRouteDefinition(definition: ProcessorDefinition[_]) = new ActorRouteDefinition(definition)
implicit def messageToRichMessage(m: Message)(implicit camel: Camel): RichMessage = new RichMessage(m, camel.context)
} }

View file

@ -1,79 +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 {
self.timeout = 1
def handler = noop
}
trait Countdown { this: Actor =>
var latch: CountDownLatch = new CountDownLatch(0)
def countdown: Handler = {
case SetExpectedMessageCount(num) => {
latch = new CountDownLatch(num)
self.reply(latch)
}
case msg => latch.countDown
}
}
trait Respond { this: Actor =>
def respond: Handler = {
case msg: Message => self.reply(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 => self.reply(messages.last)
case GetRetainedMessages(p) => self.reply(messages.toList.filter(p))
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,130 +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
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.shutdownAll
CamelContextManager.init
CamelContextManager.mandatoryContext.addRoutes(new TestRoute)
CamelContextManager.start
}
override protected def afterAll = CamelContextManager.stop
override protected def afterEach = {
Actor.registry.shutdownAll
mockEndpoint.reset
}
feature("Communicate with an actor via an actor:uuid endpoint") {
import CamelContextManager.mandatoryTemplate
scenario("one-way communication") {
val actor = actorOf[Tester1].start
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[Tester2].start
assert(mandatoryTemplate.requestBody("actor:uuid:%s" format actor.uuid, "Martin") === "Hello Martin")
}
scenario("two-way communication with timeout") {
val actor = actorOf[Tester3].start
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[Tester1].start
val latch = (actor !! SetExpectedMessageCount(1)).as[CountDownLatch].get
mandatoryTemplate.sendBody("actor:%s" format actor.id, "Martin")
assert(latch.await(5000, TimeUnit.MILLISECONDS))
val reply = (actor !! GetRetainedMessage).get.asInstanceOf[Message]
assert(reply.body === "Martin")
}
scenario("two-way communication") {
val actor = actorOf[Tester2].start
assert(mandatoryTemplate.requestBody("actor:%s" format actor.id, "Martin") === "Hello Martin")
}
scenario("two-way communication via a custom route") {
val actor = actorOf[CustomIdActor].start
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 {
self.id = "custom-id"
protected def receive = {
case msg: Message => self.reply("Received %s" format msg.body)
}
}
class FailWithMessage extends Actor {
protected def receive = {
case msg: Message => self.reply(Failure(new Exception("test")))
}
}
class FailWithException extends Actor {
protected def receive = {
case msg: Message => throw new Exception("test")
}
}
class TestRoute extends RouteBuilder {
val failWithMessage = actorOf[FailWithMessage].start
val failWithException = actorOf[FailWithException].start
def configure {
from("direct:custom-id-test-1").to("actor:custom-id")
from("direct:custom-id-test-2").to("actor:id:custom-id")
from("direct:failure-test-1")
.onException(classOf[Exception]).to("mock:mock").handled(true).end
.to("actor:uuid:%s" format failWithMessage.uuid)
from("direct:failure-test-2")
.onException(classOf[Exception]).to("mock:mock").handled(true).end
.to("actor:uuid:%s?blocking=true" format failWithException.uuid)
}
}
}

View file

@ -1,243 +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.Actor._
import akka.camel.{Failure, Message}
import akka.camel.CamelTestSupport._
class ActorProducerTest extends JUnitSuite with BeforeAndAfterAll {
import ActorProducerTest._
@After def tearDown = registry.shutdownAll
@Test def shouldSendMessageToActorWithSyncProcessor = {
val actor = actorOf[Tester1].start
val latch = (actor !! SetExpectedMessageCount(1)).as[CountDownLatch].get
val endpoint = actorEndpoint("actor:uuid:%s" format actor.uuid)
val exchange = endpoint.createExchange(ExchangePattern.InOnly)
exchange.getIn.setBody("Martin")
exchange.getIn.setHeader("k1", "v1")
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[Tester1].start
val latch = (actor !! SetExpectedMessageCount(1)).as[CountDownLatch].get
val endpoint = actorEndpoint("actor:uuid:%s" format actor.uuid)
val exchange = endpoint.createExchange(ExchangePattern.InOnly)
exchange.getIn.setBody("Martin")
exchange.getIn.setHeader("k1", "v1")
actorAsyncProducer(endpoint).process(exchange, expectSyncCompletion)
assert(latch.await(5000, TimeUnit.MILLISECONDS))
val reply = (actor !! GetRetainedMessage).get.asInstanceOf[Message]
assert(reply.body === "Martin")
assert(reply.headers === Map(Message.MessageExchangeId -> exchange.getExchangeId, "k1" -> "v1"))
}
@Test def shouldSendMessageToActorAndReceiveResponseWithSyncProcessor = {
val actor = actorOf(new Tester2 {
override def response(msg: Message) = Message(super.response(msg), Map("k2" -> "v2"))
}).start
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(new Tester2 {
override def response(msg: Message) = Message(super.response(msg), Map("k2" -> "v2"))
}).start
val completion = expectAsyncCompletion
val endpoint = actorEndpoint("actor:uuid:%s" format actor.uuid)
val exchange = endpoint.createExchange(ExchangePattern.InOut)
exchange.getIn.setBody("Martin")
exchange.getIn.setHeader("k1", "v1")
actorAsyncProducer(endpoint).process(exchange, completion)
assert(completion.latch.await(5000, TimeUnit.MILLISECONDS))
assert(exchange.getOut.getBody === "Hello Martin")
assert(exchange.getOut.getHeader("k2") === "v2")
}
@Test def shouldSendMessageToActorAndReceiveFailureWithAsyncProcessor = {
val actor = actorOf(new Tester2 {
override def response(msg: Message) = Failure(new Exception("testmsg"), Map("k3" -> "v3"))
}).start
val completion = expectAsyncCompletion
val endpoint = actorEndpoint("actor:uuid:%s" format actor.uuid)
val exchange = endpoint.createExchange(ExchangePattern.InOut)
exchange.getIn.setBody("Martin")
exchange.getIn.setHeader("k1", "v1")
actorAsyncProducer(endpoint).process(exchange, completion)
assert(completion.latch.await(5000, TimeUnit.MILLISECONDS))
assert(exchange.getException.getMessage === "testmsg")
assert(exchange.getOut.getBody === null)
assert(exchange.getOut.getHeader("k3") === null) // headers from failure message are currently ignored
}
@Test def shouldSendMessageToActorAndReceiveAckWithAsyncProcessor = {
val actor = actorOf(new Tester2 {
override def response(msg: Message) = akka.camel.Ack
}).start
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[Tester1]
val actor2 = actorOf[Tester1]
actor1.id = "x"
actor2.id = "y"
actor1.start
actor2.start
val latch1 = (actor1 !! SetExpectedMessageCount(1)).as[CountDownLatch].get
val latch2 = (actor2 !! SetExpectedMessageCount(1)).as[CountDownLatch].get
val endpoint = actorEndpoint("actor:id:%s" format actor1.id)
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.id)
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[Tester1]
val actor2 = actorOf[Tester1]
actor1.id = "x"
actor2.id = "y"
actor1.start
actor2.start
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.id)
exchange2.getIn.setHeader(ActorComponent.ActorIdentifier, actor2.id)
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[Tester1].start
val actor2 = actorOf[Tester1].start
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[Tester1].start
val actor2 = actorOf[Tester1].start
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: Unit = {
val actor = actorOf[Tester1].start
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: Unit = {
val actor = actorOf[Tester1].start
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(): Unit = {
val actor = actorOf[Tester3].start
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

@ -35,50 +35,49 @@ public class MessageJavaTestBase {
system.shutdown(); system.shutdown();
} }
Message message(Object body){ return new Message(body, new HashMap()); } CamelMessage message(Object body){ return new CamelMessage(body, new HashMap<String, Object>()); }
Message message(Object body, Map<String, Object> headers){ return new Message(body, headers); } CamelMessage message(Object body, Map<String, Object> headers){ return new CamelMessage(body, headers); }
private RichMessage rich(Message message) { return new RichMessage(message, camel.context()); }
@Test public void shouldConvertDoubleBodyToString() { @Test public void shouldConvertDoubleBodyToString() {
assertEquals("1.4", rich(message("1.4", empty)).getBodyAs(String.class)); assertEquals("1.4", message("1.4", empty).getBodyAs(String.class,camel.context()));
} }
@Test(expected=NoTypeConversionAvailableException.class) @Test(expected=NoTypeConversionAvailableException.class)
public void shouldThrowExceptionWhenConvertingDoubleBodyToInputStream() { public void shouldThrowExceptionWhenConvertingDoubleBodyToInputStream() {
rich(message(1.4)).getBodyAs(InputStream.class); message(1.4).getBodyAs(InputStream.class,camel.context());
} }
@Test public void shouldReturnDoubleHeader() { @Test public void shouldReturnDoubleHeader() {
Message message = message("test" , createMap("test", 1.4)); CamelMessage message = message("test" , createMap("test", 1.4));
assertEquals(1.4, message.getHeader("test")); assertEquals(1.4, message.getHeader("test"));
} }
@Test public void shouldConvertDoubleHeaderToString() { @Test public void shouldConvertDoubleHeaderToString() {
Message message = message("test" , createMap("test", 1.4)); CamelMessage message = message("test" , createMap("test", 1.4));
assertEquals("1.4", rich(message).getHeaderAs("test", String.class)); assertEquals("1.4", message.getHeaderAs("test", String.class,camel.context()));
} }
@Test public void shouldReturnSubsetOfHeaders() { @Test public void shouldReturnSubsetOfHeaders() {
Message message = message("test" , createMap("A", "1", "B", "2")); CamelMessage message = message("test" , createMap("A", "1", "B", "2"));
assertEquals(createMap("B", "2"), message.getHeaders(createSet("B"))); assertEquals(createMap("B", "2"), message.getHeaders(createSet("B")));
} }
@Test(expected=UnsupportedOperationException.class) @Test(expected=UnsupportedOperationException.class)
public void shouldReturnSubsetOfHeadersUnmodifiable() { public void shouldReturnSubsetOfHeadersUnmodifiable() {
Message message = message("test" , createMap("A", "1", "B", "2")); CamelMessage message = message("test" , createMap("A", "1", "B", "2"));
message.getHeaders(createSet("B")).put("x", "y"); message.getHeaders(createSet("B")).put("x", "y");
} }
@Test public void shouldReturnAllHeaders() { @Test public void shouldReturnAllHeaders() {
Message message = message("test" , createMap("A", "1", "B", "2")); CamelMessage message = message("test" , createMap("A", "1", "B", "2"));
assertEquals(createMap("A", "1", "B", "2"), message.getHeaders()); assertEquals(createMap("A", "1", "B", "2"), message.getHeaders());
} }
@Test(expected=UnsupportedOperationException.class) @Test(expected=UnsupportedOperationException.class)
public void shouldReturnAllHeadersUnmodifiable() { public void shouldReturnAllHeadersUnmodifiable() {
Message message = message("test" , createMap("A", "1", "B", "2")); CamelMessage message = message("test" , createMap("A", "1", "B", "2"));
message.getHeaders().put("x", "y"); message.getHeaders().put("x", "y");
} }
@ -91,7 +90,7 @@ public class MessageJavaTestBase {
@Test public void shouldConvertBodyAndPreserveHeaders() { @Test public void shouldConvertBodyAndPreserveHeaders() {
assertEquals( assertEquals(
message("1.4", createMap("A", "1")), message("1.4", createMap("A", "1")),
rich(message(1.4 , createMap("A", "1"))).withBodyAs(String.class)); message(1.4 , createMap("A", "1")).withBodyAs(String.class,camel.context()));
} }
@Test public void shouldSetBodyAndPreserveHeaders() { @Test public void shouldSetBodyAndPreserveHeaders() {
@ -109,13 +108,13 @@ public class MessageJavaTestBase {
@Test public void shouldAddHeaderAndPreserveBodyAndHeaders() { @Test public void shouldAddHeaderAndPreserveBodyAndHeaders() {
assertEquals( assertEquals(
message("test1" , createMap("A", "1", "B", "2")), message("test1" , createMap("A", "1", "B", "2")),
message("test1" , createMap("A", "1")).plusHeader("B", "2")); message("test1" , createMap("A", "1")).addHeader("B", "2"));
} }
@Test public void shouldAddHeadersAndPreserveBodyAndHeaders() { @Test public void shouldAddHeadersAndPreserveBodyAndHeaders() {
assertEquals( assertEquals(
message("test1" , createMap("A", "1", "B", "2")), message("test1" , createMap("A", "1", "B", "2")),
message("test1" , createMap("A", "1")).plusHeaders(createMap("B", "2"))); message("test1" , createMap("A", "1")).addHeaders(createMap("B", "2")));
} }
@Test public void shouldRemoveHeadersAndPreserveBodyAndRemainingHeaders() { @Test public void shouldRemoveHeadersAndPreserveBodyAndRemainingHeaders() {

View file

@ -34,8 +34,8 @@ public class SampleErrorHandlingConsumer extends UntypedConsumerActor {
public void onReceive(Object message) throws Exception { public void onReceive(Object message) throws Exception {
Message msg = (Message) message; CamelMessage msg = (CamelMessage) message;
String body = rich(msg).getBodyAs(String.class); String body = msg.getBodyAs(String.class,this.getCamelContext());
throw new Exception(String.format("error: %s", body)); throw new Exception(String.format("error: %s", body));
} }

View file

@ -16,9 +16,9 @@ public class SampleUntypedConsumer extends UntypedConsumerActor {
} }
public void onReceive(Object message) { public void onReceive(Object message) {
RichMessage msg = rich((Message)message); CamelMessage msg = (CamelMessage)message;
String body = msg.getBodyAs(String.class); String body = msg.getBodyAs(String.class, getCamelContext());
String header = msg.getHeaderAs("test", String.class); String header = msg.getHeaderAs("test", String.class,getCamelContext());
sender().tell(String.format("%s %s", body, header)); sender().tell(String.format("%s %s", body, header));
} }

View file

@ -16,8 +16,8 @@ public class SampleUntypedForwardingProducer extends UntypedProducerActor {
@Override @Override
public void onReceiveAfterProduce(Object message) { public void onReceiveAfterProduce(Object message) {
RichMessage msg = rich((Message)message); CamelMessage msg = (CamelMessage)message;
String body = msg.getBodyAs(String.class); String body = msg.getBodyAs(String.class,getCamelContext());
getProducerTemplate().sendBody("direct:forward-test-1", body); getProducerTemplate().sendBody("direct:forward-test-1", body);
} }
} }

View file

@ -9,9 +9,10 @@ import akka.util.duration._
import org.apache.camel.ProducerTemplate import org.apache.camel.ProducerTemplate
import akka.actor._ import akka.actor._
import akka.util.Timeout import akka.util.Timeout
import java.util.concurrent.{ TimeUnit, CountDownLatch }
import TestSupport._ import TestSupport._
import org.scalatest.WordSpec import org.scalatest.WordSpec
import akka.testkit.TestLatch
import akka.dispatch.Await
class ActivationIntegrationTest extends WordSpec with MustMatchers with SharedCamelSystem { class ActivationIntegrationTest extends WordSpec with MustMatchers with SharedCamelSystem {
implicit val timeout = Timeout(10 seconds) implicit val timeout = Timeout(10 seconds)
@ -31,21 +32,21 @@ class ActivationIntegrationTest extends WordSpec with MustMatchers with SharedCa
} }
"ActivationAware must be notified when endpoint is de-activated" in { "ActivationAware must be notified when endpoint is de-activated" in {
val stopped = new CountDownLatch(1) val latch = TestLatch()
val actor = start(new Consumer { val actor = start(new Consumer {
def endpointUri = "direct:a3" def endpointUri = "direct:a3"
def receive = { case _ {} } def receive = { case _ {} }
override def postStop() = { override def postStop() = {
super.postStop() super.postStop()
stopped.countDown() latch.countDown()
} }
}) })
camel.awaitActivation(actor, 1 second) camel.awaitActivation(actor, 1 second)
system.stop(actor) system.stop(actor)
camel.awaitDeactivation(actor, 1 second) camel.awaitDeactivation(actor, 1 second)
if (!stopped.await(1, TimeUnit.SECONDS)) fail("Actor must have stopped quickly after deactivation!") Await.ready(latch, 1 second)
} }
"ActivationAware must time out when waiting for endpoint de-activation for too long" in { "ActivationAware must time out when waiting for endpoint de-activation for too long" in {
@ -66,7 +67,7 @@ class ActivationIntegrationTest extends WordSpec with MustMatchers with SharedCa
class TestConsumer(uri: String) extends Consumer { class TestConsumer(uri: String) extends Consumer {
def endpointUri = uri def endpointUri = uri
override def receive = { override def receive = {
case msg: Message sender ! "received " + msg.body case msg: CamelMessage sender ! "received " + msg.body
} }
} }

View file

@ -26,19 +26,23 @@ class CamelExchangeAdapterTest extends FunSuite with SharedCamelSystem with Mess
} }
test("mustSetOutMessageFromResponseMessage") { test("mustSetOutMessageFromResponseMessage") {
val e1 = sampleInOut; e1.setResponse(Message("y")) val e1 = sampleInOut
e1.setResponse(Message("y"))
assert(e1.getOut.getBody === "y") assert(e1.getOut.getBody === "y")
} }
test("mustSetInMessageFromResponseMessage") { test("mustSetInMessageFromResponseMessage") {
val e1 = sampleInOnly; e1.setResponse(Message("x")) val e1 = sampleInOnly
e1.setResponse(Message("x"))
assert(e1.getIn.getBody === "x") assert(e1.getIn.getBody === "x")
} }
test("mustSetExceptionFromFailureMessage") { test("mustSetExceptionFromFailureMessage") {
val e1 = sampleInOnly; e1.setFailure(Failure(new Exception("test1"))) val e1 = sampleInOnly
e1.setFailure(Failure(new Exception("test1")))
assert(e1.getException.getMessage === "test1") assert(e1.getException.getMessage === "test1")
val e2 = sampleInOut; e2.setFailure(Failure(new Exception("test2"))) val e2 = sampleInOut
e2.setFailure(Failure(new Exception("test2")))
assert(e2.getException.getMessage === "test2") assert(e2.getException.getMessage === "test2")
} }

View file

@ -5,32 +5,31 @@
package akka.camel package akka.camel
import org.apache.camel.impl.{ DefaultExchange, DefaultMessage } import org.apache.camel.impl.{ DefaultExchange, DefaultMessage }
import org.apache.camel.{ Message CamelMessage }
import akka.camel.TestSupport.SharedCamelSystem import akka.camel.TestSupport.SharedCamelSystem
import org.scalatest.matchers.MustMatchers import org.scalatest.matchers.MustMatchers
import org.scalatest.WordSpec import org.scalatest.WordSpec
//TODO merge it with MessageScalaTest //TODO merge it with MessageScalaTest
class MessageTest extends MustMatchers with WordSpec with SharedCamelSystem { class CamelMessageTest extends MustMatchers with WordSpec with SharedCamelSystem {
"Message" must { "CamelMessage" must {
"overwrite body and add header" in { "overwrite body and add header" in {
val msg = sampleMessage val msg = sampleMessage
Message("blah", Map("key" -> "baz")).copyContentTo(msg) CamelMessage("blah", Map("key" -> "baz")).copyContentTo(msg)
assert(msg.getBody === "blah") assert(msg.getBody === "blah")
assert(msg.getHeader("foo") === "bar") assert(msg.getHeader("foo") === "bar")
assert(msg.getHeader("key") === "baz") assert(msg.getHeader("key") === "baz")
} }
"create message with body and header" in { "create message with body and header" in {
val m = Message.from(sampleMessage) val m = CamelMessage.from(sampleMessage)
assert(m.body === "test") assert(m.body === "test")
assert(m.headers("foo") === "bar") assert(m.headers("foo") === "bar")
} }
"create message with body and header and custom header" in { "create message with body and header and custom header" in {
val m = Message.from(sampleMessage, Map("key" -> "baz")) val m = CamelMessage.from(sampleMessage, Map("key" -> "baz"))
assert(m.body === "test") assert(m.body === "test")
assert(m.headers("foo") === "bar") assert(m.headers("foo") === "bar")
assert(m.headers("key") === "baz") assert(m.headers("key") === "baz")

View file

@ -7,29 +7,30 @@ package akka.camel
import akka.actor._ import akka.actor._
import org.scalatest.matchers.MustMatchers import org.scalatest.matchers.MustMatchers
import akka.util.duration._ import akka.util.duration._
import java.util.concurrent.TimeUnit._
import TestSupport._ import TestSupport._
import org.scalatest.WordSpec import org.scalatest.WordSpec
import org.apache.camel.model.RouteDefinition import org.apache.camel.model.RouteDefinition
import org.apache.camel.builder.Builder import org.apache.camel.builder.Builder
import org.apache.camel.{ FailedToCreateRouteException, CamelExecutionException } import org.apache.camel.{ FailedToCreateRouteException, CamelExecutionException }
import java.util.concurrent.{ ExecutionException, TimeUnit, TimeoutException, CountDownLatch } import java.util.concurrent.{ ExecutionException, TimeUnit, TimeoutException }
import akka.testkit.TestLatch
import akka.dispatch.Await
class ConsumerIntegrationTest extends WordSpec with MustMatchers with NonSharedCamelSystem { class ConsumerIntegrationTest extends WordSpec with MustMatchers with NonSharedCamelSystem {
private val defaultTimeout = 10
"Consumer must throw FailedToCreateRouteException, while awaiting activation, if endpoint is invalid" in { "Consumer must throw FailedToCreateRouteException, while awaiting activation, if endpoint is invalid" in {
val actorRef = system.actorOf(Props(new TestActor(uri = "some invalid uri"))) val actorRef = system.actorOf(Props(new TestActor(uri = "some invalid uri")))
intercept[FailedToCreateRouteException] { intercept[FailedToCreateRouteException] {
camel.awaitActivation(actorRef, timeout = 1 second) camel.awaitActivation(actorRef, timeout = defaultTimeout seconds)
} }
} }
"Consumer must support in-out messaging" in { "Consumer must support in-out messaging" in {
start(new Consumer { start(new Consumer {
def endpointUri = "direct:a1" def endpointUri = "direct:a1"
protected def receive = { def receive = {
case m: Message sender ! "received " + m.bodyAs[String] case m: CamelMessage sender ! "received " + m.bodyAs[String]
} }
}) })
camel.sendTo("direct:a1", msg = "some message") must be("received some message") camel.sendTo("direct:a1", msg = "some message") must be("received some message")
@ -43,7 +44,7 @@ class ConsumerIntegrationTest extends WordSpec with MustMatchers with NonSharedC
override def replyTimeout = SHORT_TIMEOUT override def replyTimeout = SHORT_TIMEOUT
def endpointUri = "direct:a3" def endpointUri = "direct:a3"
protected def receive = { case _ { Thread.sleep(LONG_WAIT.toMillis); sender ! "done" } } def receive = { case _ { Thread.sleep(LONG_WAIT.toMillis); sender ! "done" } }
}) })
val exception = intercept[CamelExecutionException] { val exception = intercept[CamelExecutionException] {
@ -53,13 +54,13 @@ class ConsumerIntegrationTest extends WordSpec with MustMatchers with NonSharedC
} }
"Consumer must process messages even after actor restart" in { "Consumer must process messages even after actor restart" in {
val restarted = new CountDownLatch(1) val restarted = TestLatch()
val consumer = start(new Consumer { val consumer = start(new Consumer {
def endpointUri = "direct:a2" def endpointUri = "direct:a2"
protected def receive = { def receive = {
case "throw" throw new Exception case "throw" throw new Exception
case m: Message sender ! "received " + m.bodyAs[String] case m: CamelMessage sender ! "received " + m.bodyAs[String]
} }
override def postRestart(reason: Throwable) { override def postRestart(reason: Throwable) {
@ -67,7 +68,7 @@ class ConsumerIntegrationTest extends WordSpec with MustMatchers with NonSharedC
} }
}) })
consumer ! "throw" consumer ! "throw"
if (!restarted.await(1, SECONDS)) fail("Actor failed to restart!") Await.ready(restarted, defaultTimeout seconds)
val response = camel.sendTo("direct:a2", msg = "xyz") val response = camel.sendTo("direct:a2", msg = "xyz")
response must be("received xyz") response must be("received xyz")
@ -75,12 +76,12 @@ class ConsumerIntegrationTest extends WordSpec with MustMatchers with NonSharedC
"Consumer must unregister itself when stopped" in { "Consumer must unregister itself when stopped" in {
val consumer = start(new TestActor()) val consumer = start(new TestActor())
camel.awaitActivation(consumer, 1 second) camel.awaitActivation(consumer, defaultTimeout seconds)
camel.routeCount must be > (0) camel.routeCount must be > (0)
system.stop(consumer) system.stop(consumer)
camel.awaitDeactivation(consumer, 1 second) camel.awaitDeactivation(consumer, defaultTimeout seconds)
camel.routeCount must be(0) camel.routeCount must be(0)
} }
@ -106,20 +107,20 @@ class ConsumerIntegrationTest extends WordSpec with MustMatchers with NonSharedC
"Consumer supports manual Ack" in { "Consumer supports manual Ack" in {
start(new ManualAckConsumer() { start(new ManualAckConsumer() {
def endpointUri = "direct:manual-ack" def endpointUri = "direct:manual-ack"
protected def receive = { case _ sender ! Ack } def receive = { case _ sender ! Ack }
}) })
camel.template.asyncSendBody("direct:manual-ack", "some message").get(1, TimeUnit.SECONDS) must be(null) //should not timeout camel.template.asyncSendBody("direct:manual-ack", "some message").get(defaultTimeout, TimeUnit.SECONDS) must be(null) //should not timeout
} }
"Consumer handles manual Ack failure" in { "Consumer handles manual Ack failure" in {
val someException = new Exception("e1") val someException = new Exception("e1")
start(new ManualAckConsumer() { start(new ManualAckConsumer() {
def endpointUri = "direct:manual-ack" def endpointUri = "direct:manual-ack"
protected def receive = { case _ sender ! Failure(someException) } def receive = { case _ sender ! Failure(someException) }
}) })
intercept[ExecutionException] { intercept[ExecutionException] {
camel.template.asyncSendBody("direct:manual-ack", "some message").get(1, TimeUnit.SECONDS) camel.template.asyncSendBody("direct:manual-ack", "some message").get(defaultTimeout, TimeUnit.SECONDS)
}.getCause.getCause must be(someException) }.getCause.getCause must be(someException)
} }
@ -127,25 +128,25 @@ class ConsumerIntegrationTest extends WordSpec with MustMatchers with NonSharedC
start(new ManualAckConsumer() { start(new ManualAckConsumer() {
override def replyTimeout = 10 millis override def replyTimeout = 10 millis
def endpointUri = "direct:manual-ack" def endpointUri = "direct:manual-ack"
protected def receive = { case _ } def receive = { case _ }
}) })
intercept[ExecutionException] { intercept[ExecutionException] {
camel.template.asyncSendBody("direct:manual-ack", "some message").get(1, TimeUnit.SECONDS) camel.template.asyncSendBody("direct:manual-ack", "some message").get(defaultTimeout, TimeUnit.SECONDS)
}.getCause.getCause.getMessage must include("Failed to get Ack") }.getCause.getCause.getMessage must include("Failed to get Ack")
} }
} }
class ErrorThrowingConsumer(override val endpointUri: String) extends Consumer { class ErrorThrowingConsumer(override val endpointUri: String) extends Consumer {
def receive = { def receive = {
case msg: Message throw new Exception("error: %s" format msg.body) case msg: CamelMessage throw new Exception("error: %s" format msg.body)
} }
} }
class FailingOnceConsumer(override val endpointUri: String) extends Consumer { class FailingOnceConsumer(override val endpointUri: String) extends Consumer {
def receive = { def receive = {
case msg: Message case msg: CamelMessage
if (msg.headerAs[Boolean]("CamelRedelivered").getOrElse(false)) if (msg.headerAs[Boolean]("CamelRedelivered").getOrElse(false))
sender ! ("accepted: %s" format msg.body) sender ! ("accepted: %s" format msg.body)
else else
@ -155,5 +156,16 @@ class FailingOnceConsumer(override val endpointUri: String) extends Consumer {
class TestActor(uri: String = "file://target/abcde") extends Consumer { class TestActor(uri: String = "file://target/abcde") extends Consumer {
def endpointUri = uri def endpointUri = uri
protected def receive = { case _ /* do nothing */ } 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

@ -12,7 +12,7 @@ import org.scalatest.FunSuite
import org.scalatest.matchers.MustMatchers import org.scalatest.matchers.MustMatchers
class MessageScalaTest extends FunSuite with MustMatchers with SharedCamelSystem with MessageSugar { class MessageScalaTest extends FunSuite with MustMatchers with SharedCamelSystem with MessageSugar {
implicit def camelContext = camel.context
test("mustConvertDoubleBodyToString") { test("mustConvertDoubleBodyToString") {
Message(1.4).bodyAs[String] must be("1.4") Message(1.4).bodyAs[String] must be("1.4")
} }
@ -59,13 +59,13 @@ class MessageScalaTest extends FunSuite with MustMatchers with SharedCamelSystem
} }
test("mustAddHeaderAndPreserveBodyAndHeaders") { test("mustAddHeaderAndPreserveBodyAndHeaders") {
Message("test1", Map("A" -> "1")).plusHeader("B" -> "2") must be( Message("test1", Map("A" -> "1")).addHeader("B" -> "2") must be(
Message("test1", Map("A" -> "1", "B" -> "2"))) Message("test1", Map("A" -> "1", "B" -> "2")))
} }
test("mustAddHeadersAndPreserveBodyAndHeaders") { test("mustAddHeadersAndPreserveBodyAndHeaders") {
Message("test1", Map("A" -> "1")).plusHeaders(Map("B" -> "2")) must be( Message("test1", Map("A" -> "1")).addHeaders(Map("B" -> "2")) must be(
Message("test1", Map("A" -> "1", "B" -> "2"))) Message("test1", Map("A" -> "1", "B" -> "2")))
} }

View file

@ -7,17 +7,17 @@ package akka.camel
import org.apache.camel.{ Exchange, Processor } import org.apache.camel.{ Exchange, Processor }
import org.apache.camel.builder.RouteBuilder import org.apache.camel.builder.RouteBuilder
import org.apache.camel.component.mock.MockEndpoint import org.apache.camel.component.mock.MockEndpoint
import org.scalatest.{ GivenWhenThen, BeforeAndAfterEach, BeforeAndAfterAll, FeatureSpec }
import akka.actor._ import akka.actor._
import akka.pattern._ import akka.pattern._
import akka.dispatch.Await import akka.dispatch.Await
import akka.util.duration._ import akka.util.duration._
import akka.camel.TestSupport.SharedCamelSystem import akka.camel.TestSupport.SharedCamelSystem
import org.scalatest._
/** /**
* Tests the features of the Camel Producer. * Tests the features of the Camel Producer.
*/ */
class ProducerFeatureTest extends FeatureSpec with BeforeAndAfterAll with BeforeAndAfterEach with SharedCamelSystem with GivenWhenThen { class ProducerFeatureTest extends WordSpec with BeforeAndAfterAll with BeforeAndAfterEach with SharedCamelSystem with GivenWhenThen {
import ProducerFeatureTest._ import ProducerFeatureTest._
@ -31,158 +31,153 @@ class ProducerFeatureTest extends FeatureSpec with BeforeAndAfterAll with Before
override protected def afterEach { mockEndpoint.reset() } override protected def afterEach { mockEndpoint.reset() }
feature("Producer on a sync Camel route") { "A Producer on a sync Camel route" must {
scenario("produces a message and receives normal response") { "produce a message and receive normal response" in {
given("a registered two-way producer") given("a registered two-way producer")
val producer = system.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 ?") when("a test message is sent to the producer with ?")
val message = Message("test", Map(Message.MessageExchangeId -> "123")) val message = CamelMessage("test", Map(CamelMessage.MessageExchangeId -> "123"))
val future = producer.ask(message)(timeout) val future = producer.ask(message)(timeout)
then("a normal response must have been returned by the producer") then("a normal response must have been returned by the producer")
val expected = Message("received TEST", Map(Message.MessageExchangeId -> "123")) val expected = CamelMessage("received TEST", Map(CamelMessage.MessageExchangeId -> "123"))
Await.result(future, timeout) match { Await.result(future, timeout) match {
case result: Message assert(result === expected) case result: CamelMessage assert(result === expected)
case unexpected fail("Actor responded with unexpected message:" + unexpected) case unexpected fail("Actor responded with unexpected message:" + unexpected)
} }
} }
scenario("produces a message and receives failure response") { "produce a message and receive failure response" in {
given("a registered two-way producer") given("a registered two-way producer")
val producer = system.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 ?") when("a test message causing an exception is sent to the producer with ?")
val message = Message("fail", Map(Message.MessageExchangeId -> "123")) val message = CamelMessage("fail", Map(CamelMessage.MessageExchangeId -> "123"))
val future = producer.ask(message)(timeout) val future = producer.ask(message)(timeout)
Await.result(future, timeout) match { Await.result(future, timeout) match {
case result: Failure { case result: Failure
then("a failure response must have been returned by the producer") then("a failure response must have been returned by the producer")
val expectedFailureText = result.cause.getMessage val expectedFailureText = result.cause.getMessage
val expectedHeaders = result.headers val expectedHeaders = result.headers
assert(expectedFailureText === "failure") assert(expectedFailureText === "failure")
assert(expectedHeaders === Map(Message.MessageExchangeId -> "123")) assert(expectedHeaders === Map(CamelMessage.MessageExchangeId -> "123"))
}
case unexpected fail("Actor responded with unexpected message:" + unexpected) case unexpected fail("Actor responded with unexpected message:" + unexpected)
} }
} }
scenario("produces message oneway") { "produce a message oneway" in {
given("a registered one-way producer") given("a registered one-way producer")
val producer = system.actorOf(Props(new TestProducer("direct:producer-test-1", true) with Oneway)) val producer = system.actorOf(Props(new TestProducer("direct:producer-test-1", true) with Oneway))
when("a test message is sent to the producer with !") when("a test message is sent to the producer with !")
mockEndpoint.expectedBodiesReceived("TEST") mockEndpoint.expectedBodiesReceived("TEST")
producer ! Message("test", Map()) producer ! CamelMessage("test", Map())
then("the test message must have been sent to mock:mock") then("the test message must have been sent to mock:mock")
mockEndpoint.assertIsSatisfied() mockEndpoint.assertIsSatisfied()
} }
scenario("produces message twoway without sender reference") { "produces message twoway without sender reference" in {
given("a registered two-way producer") given("a registered two-way producer")
val producer = system.actorOf(Props(new TestProducer("direct:producer-test-1"))) val producer = system.actorOf(Props(new TestProducer("direct:producer-test-1")))
when("a test message is sent to the producer with !") when("a test message is sent to the producer with !")
mockEndpoint.expectedBodiesReceived("test") mockEndpoint.expectedBodiesReceived("test")
producer ! Message("test", Map()) producer ! CamelMessage("test", Map())
then("there must be only a warning that there's no sender reference") then("there must be only a warning that there's no sender reference")
mockEndpoint.assertIsSatisfied() mockEndpoint.assertIsSatisfied()
} }
} }
feature("Producer on an async Camel route") { "A Producer on an async Camel route" must {
scenario("produces message to direct:producer-test-3 and receives normal response") { "produce message to direct:producer-test-3 and receive normal response" in {
given("a registered two-way producer") given("a registered two-way producer")
val producer = system.actorOf(Props(new TestProducer("direct:producer-test-3"))) val producer = system.actorOf(Props(new TestProducer("direct:producer-test-3")))
when("a test message is sent to the producer with ?") when("a test message is sent to the producer with ?")
val message = Message("test", Map(Message.MessageExchangeId -> "123")) val message = CamelMessage("test", Map(CamelMessage.MessageExchangeId -> "123"))
val future = producer.ask(message)(timeout) val future = producer.ask(message)(timeout)
Await.result(future, timeout) match { Await.result(future, timeout) match {
case result: Message { case result: CamelMessage
then("a normal response must have been returned by the producer") then("a normal response must have been returned by the producer")
val expected = Message("received test", Map(Message.MessageExchangeId -> "123")) val expected = CamelMessage("received test", Map(CamelMessage.MessageExchangeId -> "123"))
assert(result === expected) assert(result === expected)
}
case unexpected fail("Actor responded with unexpected message:" + unexpected) case unexpected fail("Actor responded with unexpected message:" + unexpected)
} }
} }
scenario("produces message to direct:producer-test-3 and receives failure response") { "produce message to direct:producer-test-3 and receive failure response" in {
given("a registered two-way producer") given("a registered two-way producer")
val producer = system.actorOf(Props(new TestProducer("direct:producer-test-3"))) val producer = system.actorOf(Props(new TestProducer("direct:producer-test-3")))
when("a test message causing an exception is sent to the producer with ?") when("a test message causing an exception is sent to the producer with ?")
val message = Message("fail", Map(Message.MessageExchangeId -> "123")) val message = CamelMessage("fail", Map(CamelMessage.MessageExchangeId -> "123"))
val future = producer.ask(message)(timeout) val future = producer.ask(message)(timeout)
Await.result(future, timeout) match { Await.result(future, timeout) match {
case result: Failure { case result: Failure
then("a failure response must have been returned by the producer") then("a failure response must have been returned by the producer")
val expectedFailureText = result.cause.getMessage val expectedFailureText = result.cause.getMessage
val expectedHeaders = result.headers val expectedHeaders = result.headers
assert(expectedFailureText === "failure") assert(expectedFailureText === "failure")
assert(expectedHeaders === Map(Message.MessageExchangeId -> "123")) assert(expectedHeaders === Map(CamelMessage.MessageExchangeId -> "123"))
}
case unexpected fail("Actor responded with unexpected message:" + unexpected) case unexpected fail("Actor responded with unexpected message:" + unexpected)
} }
} }
scenario("produces message, forwards normal response of direct:producer-test-2 to a replying target actor and receives response") { "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") given("a registered two-way producer configured with a forward target")
val target = system.actorOf(Props[ReplyingForwardTarget]) val target = system.actorOf(Props[ReplyingForwardTarget])
val producer = system.actorOf(Props(new TestForwarder("direct:producer-test-2", target))) val producer = system.actorOf(Props(new TestForwarder("direct:producer-test-2", target)))
when("a test message is sent to the producer with ?") when("a test message is sent to the producer with ?")
val message = Message("test", Map(Message.MessageExchangeId -> "123")) val message = CamelMessage("test", Map(CamelMessage.MessageExchangeId -> "123"))
val future = producer.ask(message)(timeout) val future = producer.ask(message)(timeout)
Await.result(future, timeout) match { Await.result(future, timeout) match {
case result: Message { case result: CamelMessage
then("a normal response must have been returned by the forward target") then("a normal response must have been returned by the forward target")
val expected = Message("received test", Map(Message.MessageExchangeId -> "123", "test" -> "result")) val expected = CamelMessage("received test", Map(CamelMessage.MessageExchangeId -> "123", "test" -> "result"))
assert(result === expected) assert(result === expected)
}
case unexpected fail("Actor responded with unexpected message:" + unexpected) case unexpected fail("Actor responded with unexpected message:" + unexpected)
} }
} }
scenario("produces message, forwards failure response of direct:producer-test-2 to a replying target actor and receives response") { "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") given("a registered two-way producer configured with a forward target")
val target = system.actorOf(Props[ReplyingForwardTarget]) val target = system.actorOf(Props[ReplyingForwardTarget])
val producer = system.actorOf(Props(new TestForwarder("direct:producer-test-2", target))) 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 ?") when("a test message causing an exception is sent to the producer with ?")
val message = Message("fail", Map(Message.MessageExchangeId -> "123")) val message = CamelMessage("fail", Map(CamelMessage.MessageExchangeId -> "123"))
val future = producer.ask(message)(timeout) val future = producer.ask(message)(timeout)
Await.result(future, timeout) match { Await.result(future, timeout) match {
case failure: Failure { case failure: Failure
then("a failure response must have been returned by the forward target") then("a failure response must have been returned by the forward target")
val expectedFailureText = failure.cause.getMessage val expectedFailureText = failure.cause.getMessage
val expectedHeaders = failure.headers val expectedHeaders = failure.headers
assert(expectedFailureText === "failure") assert(expectedFailureText === "failure")
assert(expectedHeaders === Map(Message.MessageExchangeId -> "123", "test" -> "failure")) assert(expectedHeaders === Map(CamelMessage.MessageExchangeId -> "123", "test" -> "failure"))
}
case unexpected fail("Actor responded with unexpected message:" + unexpected) case unexpected fail("Actor responded with unexpected message:" + unexpected)
} }
} }
scenario("produces message, forwards normal response to a producing target actor and produces response to direct:forward-test-1") { "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") given("a registered one-way producer configured with a forward target")
val target = system.actorOf(Props[ProducingForwardTarget]) val target = system.actorOf(Props[ProducingForwardTarget])
val producer = system.actorOf(Props(new TestForwarder("direct:producer-test-2", target))) val producer = system.actorOf(Props(new TestForwarder("direct:producer-test-2", target)))
when("a test message is sent to the producer with !") when("a test message is sent to the producer with !")
mockEndpoint.expectedBodiesReceived("received test") mockEndpoint.expectedBodiesReceived("received test")
producer.tell(Message("test", Map()), producer) producer.tell(CamelMessage("test", Map()), producer)
then("a normal response must have been produced by the forward target") then("a normal response must have been produced by the forward target")
mockEndpoint.assertIsSatisfied() mockEndpoint.assertIsSatisfied()
} }
scenario("produces message, forwards failure response to a producing target actor and produces 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") given("a registered one-way producer configured with a forward target")
val target = system.actorOf(Props[ProducingForwardTarget]) val target = system.actorOf(Props[ProducingForwardTarget])
@ -191,66 +186,64 @@ class ProducerFeatureTest extends FeatureSpec with BeforeAndAfterAll with Before
when("a test message causing an exception is sent to the producer with !") when("a test message causing an exception is sent to the producer with !")
mockEndpoint.expectedMessageCount(1) mockEndpoint.expectedMessageCount(1)
mockEndpoint.message(0).body().isInstanceOf(classOf[Failure]) mockEndpoint.message(0).body().isInstanceOf(classOf[Failure])
producer.tell(Message("fail", Map()), producer) producer.tell(CamelMessage("fail", Map()), producer)
then("a failure response must have been produced by the forward target") then("a failure response must have been produced by the forward target")
mockEndpoint.assertIsSatisfied() mockEndpoint.assertIsSatisfied()
} }
scenario("produces message, forwards normal response from direct:producer-test-3 to a replying target actor and receives response") { "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") given("a registered two-way producer configured with a forward target")
val target = system.actorOf(Props[ReplyingForwardTarget]) val target = system.actorOf(Props[ReplyingForwardTarget])
val producer = system.actorOf(Props(new TestForwarder("direct:producer-test-3", target))) val producer = system.actorOf(Props(new TestForwarder("direct:producer-test-3", target)))
when("a test message is sent to the producer with ?") when("a test message is sent to the producer with ?")
val message = Message("test", Map(Message.MessageExchangeId -> "123")) val message = CamelMessage("test", Map(CamelMessage.MessageExchangeId -> "123"))
val future = producer.ask(message)(timeout) val future = producer.ask(message)(timeout)
then("a normal response must have been returned by the forward target") then("a normal response must have been returned by the forward target")
Await.result(future, timeout) match { Await.result(future, timeout) match {
case message: Message { case message: CamelMessage
val expected = Message("received test", Map(Message.MessageExchangeId -> "123", "test" -> "result")) val expected = CamelMessage("received test", Map(CamelMessage.MessageExchangeId -> "123", "test" -> "result"))
assert(message === expected) assert(message === expected)
}
case unexpected fail("Actor responded with unexpected message:" + unexpected) case unexpected fail("Actor responded with unexpected message:" + unexpected)
} }
} }
scenario("produces message, forwards failure response from direct:producer-test-3 to a replying target actor and receives response") { "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") given("a registered two-way producer configured with a forward target")
val target = system.actorOf(Props[ReplyingForwardTarget]) val target = system.actorOf(Props[ReplyingForwardTarget])
val producer = system.actorOf(Props(new TestForwarder("direct:producer-test-3", target))) 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 !!") when("a test message causing an exception is sent to the producer with !!")
val message = Message("fail", Map(Message.MessageExchangeId -> "123")) val message = CamelMessage("fail", Map(CamelMessage.MessageExchangeId -> "123"))
val future = producer.ask(message)(timeout) val future = producer.ask(message)(timeout)
Await.result(future, timeout) match { Await.result(future, timeout) match {
case failure: Failure { case failure: Failure
then("a failure response must have been returned by the forward target") then("a failure response must have been returned by the forward target")
val expectedFailureText = failure.cause.getMessage val expectedFailureText = failure.cause.getMessage
val expectedHeaders = failure.headers val expectedHeaders = failure.headers
assert(expectedFailureText === "failure") assert(expectedFailureText === "failure")
assert(expectedHeaders === Map(Message.MessageExchangeId -> "123", "test" -> "failure")) assert(expectedHeaders === Map(CamelMessage.MessageExchangeId -> "123", "test" -> "failure"))
}
case unexpected fail("Actor responded with unexpected message:" + unexpected) case unexpected fail("Actor responded with unexpected message:" + unexpected)
} }
} }
scenario("produces message, forwards normal response from direct:producer-test-3 to a producing target actor and produces response to direct:forward-test-1") { "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") given("a registered one-way producer configured with a forward target")
val target = system.actorOf(Props[ProducingForwardTarget]) val target = system.actorOf(Props[ProducingForwardTarget])
val producer = system.actorOf(Props(new TestForwarder("direct:producer-test-3", target))) val producer = system.actorOf(Props(new TestForwarder("direct:producer-test-3", target)))
when("a test message is sent to the producer with !") when("a test message is sent to the producer with !")
mockEndpoint.expectedBodiesReceived("received test") mockEndpoint.expectedBodiesReceived("received test")
producer.tell(Message("test", Map()), producer) producer.tell(CamelMessage("test", Map()), producer)
then("a normal response must have been produced by the forward target") then("a normal response must have been produced by the forward target")
mockEndpoint.assertIsSatisfied() mockEndpoint.assertIsSatisfied()
} }
scenario("produces message, forwards failure response from direct:producer-test-3 to a producing target actor and produces response to direct:forward-test-1") { "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") given("a registered one-way producer configured with a forward target")
val target = system.actorOf(Props[ProducingForwardTarget]) val target = system.actorOf(Props[ProducingForwardTarget])
val producer = system.actorOf(Props(new TestForwarder("direct:producer-test-3", target))) val producer = system.actorOf(Props(new TestForwarder("direct:producer-test-3", target)))
@ -258,7 +251,7 @@ class ProducerFeatureTest extends FeatureSpec with BeforeAndAfterAll with Before
when("a test message causing an exception is sent to the producer with !") when("a test message causing an exception is sent to the producer with !")
mockEndpoint.expectedMessageCount(1) mockEndpoint.expectedMessageCount(1)
mockEndpoint.message(0).body().isInstanceOf(classOf[Failure]) mockEndpoint.message(0).body().isInstanceOf(classOf[Failure])
producer.tell(Message("fail", Map()), producer) producer.tell(CamelMessage("fail", Map()), producer)
then("a failure response must have been produced by the forward target") then("a failure response must have been produced by the forward target")
mockEndpoint.assertIsSatisfied() mockEndpoint.assertIsSatisfied()
@ -274,7 +267,7 @@ object ProducerFeatureTest {
def endpointUri = uri def endpointUri = uri
override protected def receiveBeforeProduce = { override protected def receiveBeforeProduce = {
case msg: Message if (upper) msg.mapBody { case msg: CamelMessage if (upper) msg.mapBody {
body: String body.toUpperCase body: String body.toUpperCase
} }
else msg else msg
@ -291,23 +284,20 @@ object ProducerFeatureTest {
class TestResponder extends Actor { class TestResponder extends Actor {
protected def receive = { protected def receive = {
case msg: Message msg.body match { case msg: CamelMessage msg.body match {
case "fail" { case "fail" context.sender ! (Failure(new Exception("failure"), msg.headers))
context.sender ! (Failure(new Exception("failure"), msg.headers)) case _
}
case bod: Any {
context.sender ! (msg.mapBody { context.sender ! (msg.mapBody {
body: String "received %s" format body body: String "received %s" format body
}) })
}
} }
} }
} }
class ReplyingForwardTarget extends Actor { class ReplyingForwardTarget extends Actor {
protected def receive = { protected def receive = {
case msg: Message case msg: CamelMessage
context.sender ! (msg.plusHeader("test" -> "result")) context.sender ! (msg.addHeader("test" -> "result"))
case msg: Failure case msg: Failure
context.sender ! (Failure(msg.cause, msg.headers + ("test" -> "failure"))) context.sender ! (Failure(msg.cause, msg.headers + ("test" -> "failure")))
} }

View file

@ -41,8 +41,8 @@ private[camel] object TestSupport {
@deprecated @deprecated
trait MessageSugar { trait MessageSugar {
def Message(body: Any) = akka.camel.Message(body, Map.empty) def Message(body: Any) = akka.camel.CamelMessage(body, Map.empty)
def Message(body: Any, headers: Map[String, Any]) = akka.camel.Message(body, headers) def Message(body: Any, headers: Map[String, Any]) = akka.camel.CamelMessage(body, headers)
} }
trait SharedCamelSystem extends BeforeAndAfterAll { this: Suite trait SharedCamelSystem extends BeforeAndAfterAll { this: Suite

View file

@ -33,13 +33,13 @@ class UntypedProducerTest extends WordSpec with BeforeAndAfterAll with BeforeAnd
val producer = system.actorOf(Props[SampleUntypedReplyingProducer]) val producer = system.actorOf(Props[SampleUntypedReplyingProducer])
when("a test message is sent to the producer with !!") when("a test message is sent to the producer with !!")
val message = Message("test", Map(Message.MessageExchangeId -> "123")) val message = CamelMessage("test", Map(CamelMessage.MessageExchangeId -> "123"))
val future = producer.ask(message)(timeout) val future = producer.ask(message)(timeout)
then("a normal response should have been returned by the producer") then("a normal response should have been returned by the producer")
val expected = Message("received test", Map(Message.MessageExchangeId -> "123")) val expected = CamelMessage("received test", Map(CamelMessage.MessageExchangeId -> "123"))
Await.result(future, timeout) match { Await.result(future, timeout) match {
case result: Message assert(result === expected) case result: CamelMessage assert(result === expected)
case unexpected fail("Actor responded with unexpected message:" + unexpected) case unexpected fail("Actor responded with unexpected message:" + unexpected)
} }
} }
@ -49,7 +49,7 @@ class UntypedProducerTest extends WordSpec with BeforeAndAfterAll with BeforeAnd
val producer = system.actorOf(Props[SampleUntypedReplyingProducer]) val producer = system.actorOf(Props[SampleUntypedReplyingProducer])
when("a test message causing an exception is sent to the producer with !!") when("a test message causing an exception is sent to the producer with !!")
val message = Message("fail", Map(Message.MessageExchangeId -> "123")) val message = CamelMessage("fail", Map(CamelMessage.MessageExchangeId -> "123"))
val future = producer.ask(message)(timeout) val future = producer.ask(message)(timeout)
then("a failure response should have been returned by the producer") then("a failure response should have been returned by the producer")
Await.result(future, timeout) match { Await.result(future, timeout) match {
@ -57,7 +57,7 @@ class UntypedProducerTest extends WordSpec with BeforeAndAfterAll with BeforeAnd
val expectedFailureText = result.cause.getMessage val expectedFailureText = result.cause.getMessage
val expectedHeaders = result.headers val expectedHeaders = result.headers
assert(expectedFailureText === "failure") assert(expectedFailureText === "failure")
assert(expectedHeaders === Map(Message.MessageExchangeId -> "123")) assert(expectedHeaders === Map(CamelMessage.MessageExchangeId -> "123"))
} }
case unexpected fail("Actor responded with unexpected message:" + unexpected) case unexpected fail("Actor responded with unexpected message:" + unexpected)
} }
@ -73,7 +73,7 @@ class UntypedProducerTest extends WordSpec with BeforeAndAfterAll with BeforeAnd
when("a test message is sent to the producer with !") when("a test message is sent to the producer with !")
mockEndpoint.expectedBodiesReceived("received test") mockEndpoint.expectedBodiesReceived("received test")
val result = producer.tell(Message("test", Map[String, Any]()), producer) val result = producer.tell(CamelMessage("test", Map[String, Any]()), producer)
then("a normal response should have been sent") then("a normal response should have been sent")
mockEndpoint.assertIsSatisfied mockEndpoint.assertIsSatisfied

View file

@ -1,63 +0,0 @@
package akka.camel.internal
/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
import akka.camel.internal.Try._
import org.scalatest.matchers.MustMatchers
import org.scalatest.mock.MockitoSugar
import akka.event.LoggingAdapter
import org.scalatest.{ BeforeAndAfterEach, WordSpec }
class TryTest extends WordSpec with MustMatchers with MockitoSugar with BeforeAndAfterEach {
import org.mockito.Mockito._
import org.mockito.Matchers._
import org.mockito.Matchers.{ eq the }
implicit var log: LoggingAdapter = _
override def beforeEach() {
log = mock[LoggingAdapter]
}
"Safe executes block" in {
var executed = false
safe {
executed = true
}
executed must be(true)
verifyNoMoreInteractions(log)
}
"Safe swallows exception and logs it" in {
safe(throw new Exception)
verify(log).warning(any[String], any[Any])
}
"Try-otherwise runs otherwise and throws exception when the first block fails" in {
var otherwiseCalled = false
intercept[Exception] {
Try(throw new Exception) otherwise (otherwiseCalled = true)
}
otherwiseCalled must be(true)
verifyNoMoreInteractions(log)
}
"Try-otherwise swallows exception thrown in otherwise clause and logs it" in {
val exceptionFromOtherwise = new RuntimeException("e2")
intercept[RuntimeException] {
Try(throw new RuntimeException("e1")) otherwise (throw exceptionFromOtherwise)
}.getMessage must be("e1")
verify(log, only()).warning(any[String], the(exceptionFromOtherwise))
}
"Try-otherwise doesnt run otherwise if first block doesnt fail" in {
var otherwiseCalled = false
Try(2 + 2) otherwise (otherwiseCalled = true)
otherwiseCalled must be(false)
verifyNoMoreInteractions(log)
}
}

View file

@ -31,7 +31,7 @@ class ActorProducerTest extends TestKit(ActorSystem("test")) with WordSpec with
"consumer actor doesnt exist" must { "consumer actor doesnt exist" must {
"set failure message on exchange" in { "set failure message on exchange" in {
producer = given(actor = null) producer = given(actor = null)
producer.process(exchange) producer.processExchangeAdapter(exchange)
verify(exchange).setFailure(any[Failure]) verify(exchange).setFailure(any[Failure])
} }
@ -41,12 +41,12 @@ class ActorProducerTest extends TestKit(ActorSystem("test")) with WordSpec with
def producer = given(outCapable = false) def producer = given(outCapable = false)
"pass the message to the consumer" in { "pass the message to the consumer" in {
producer.process(exchange) producer.processExchangeAdapter(exchange)
within(1 second)(probe.expectMsg(message)) within(1 second)(probe.expectMsg(message))
} }
"not expect response and not block" in { "not expect response and not block" in {
time(producer.process(exchange)) must be < (30 millis) time(producer.processExchangeAdapter(exchange)) must be < (30 millis)
} }
} }
@ -57,7 +57,7 @@ class ActorProducerTest extends TestKit(ActorSystem("test")) with WordSpec with
"get a response" in { "get a response" in {
producer = given(actor = echoActor, outCapable = true) producer = given(actor = echoActor, outCapable = true)
producer.process(exchange) producer.processExchangeAdapter(exchange)
verify(exchange).setResponse(msg("received " + message)) verify(exchange).setResponse(msg("received " + message))
} }
@ -67,17 +67,17 @@ class ActorProducerTest extends TestKit(ActorSystem("test")) with WordSpec with
def process() = { def process() = {
producer = given(outCapable = true, replyTimeout = 100 millis) producer = given(outCapable = true, replyTimeout = 100 millis)
time(producer.process(exchange)) time(producer.processExchangeAdapter(exchange))
} }
"timeout after replyTimeout" in { "timeout after replyTimeout" in {
val duration = process() val duration = process()
duration must (be >= (100 millis) and be < (200 millis)) duration must (be >= (100 millis) and be < (300 millis))
} }
"never set the response on exchange" in { "never set the response on exchange" in {
process() process()
verify(exchange, Mockito.never()).setResponse(any[Message]) verify(exchange, Mockito.never()).setResponse(any[CamelMessage])
} }
"set failure message to timeout" in { "set failure message to timeout" in {
@ -95,7 +95,7 @@ class ActorProducerTest extends TestKit(ActorSystem("test")) with WordSpec with
"asynchronous" when { "asynchronous" when {
def verifyFailureIsSet { def verifyFailureIsSet {
producer.process(exchange, asyncCallback) producer.processExchangeAdapter(exchange, asyncCallback)
asyncCallback.awaitCalled() asyncCallback.awaitCalled()
verify(exchange).setFailure(any[Failure]) verify(exchange).setFailure(any[Failure])
} }
@ -113,12 +113,12 @@ class ActorProducerTest extends TestKit(ActorSystem("test")) with WordSpec with
"get a response and async callback as soon as it gets the response (but not before)" in { "get a response and async callback as soon as it gets the response (but not before)" in {
producer = given(outCapable = true) producer = given(outCapable = true)
val doneSync = producer.process(exchange, asyncCallback) val doneSync = producer.processExchangeAdapter(exchange, asyncCallback)
asyncCallback.expectNoCallWithin(100 millis); info("no async callback before response") asyncCallback.expectNoCallWithin(100 millis); info("no async callback before response")
within(1 second) { within(1 second) {
probe.expectMsgType[Message] probe.expectMsgType[CamelMessage]
probe.sender ! "some message" probe.sender ! "some message"
} }
doneSync must be(false); info("done async") doneSync must be(false); info("done async")
@ -134,10 +134,10 @@ class ActorProducerTest extends TestKit(ActorSystem("test")) with WordSpec with
producer = given(outCapable = true) producer = given(outCapable = true)
producer.process(exchange, asyncCallback) producer.processExchangeAdapter(exchange, asyncCallback)
within(1 second) { within(1 second) {
probe.expectMsgType[Message] probe.expectMsgType[CamelMessage]
probe.sender ! failure probe.sender ! failure
asyncCallback.awaitCalled(remaining) asyncCallback.awaitCalled(remaining)
} }
@ -149,7 +149,7 @@ class ActorProducerTest extends TestKit(ActorSystem("test")) with WordSpec with
"no response is sent within timeout" must { "no response is sent within timeout" must {
"set TimeoutException on exchange" in { "set TimeoutException on exchange" in {
producer = given(outCapable = true, replyTimeout = 10 millis) producer = given(outCapable = true, replyTimeout = 10 millis)
producer.process(exchange, asyncCallback) producer.processExchangeAdapter(exchange, asyncCallback)
asyncCallback.awaitCalled(100 millis) asyncCallback.awaitCalled(100 millis)
verify(exchange).setFailure(Matchers.argThat(new ArgumentMatcher[Failure] { verify(exchange).setFailure(Matchers.argThat(new ArgumentMatcher[Failure] {
def matches(failure: AnyRef) = { failure.asInstanceOf[Failure].getCause must be(anInstanceOf[TimeoutException]); true } def matches(failure: AnyRef) = { failure.asInstanceOf[Failure].getCause must be(anInstanceOf[TimeoutException]); true }
@ -174,11 +174,11 @@ class ActorProducerTest extends TestKit(ActorSystem("test")) with WordSpec with
"get sync callback as soon as it sends a message" in { "get sync callback as soon as it sends a message" in {
producer = given(outCapable = false, autoAck = true) producer = given(outCapable = false, autoAck = true)
val doneSync = producer.process(exchange, asyncCallback) val doneSync = producer.processExchangeAdapter(exchange, asyncCallback)
doneSync must be(true); info("done sync") doneSync must be(true); info("done sync")
asyncCallback.expectDoneSyncWithin(1 second); info("async callback called") asyncCallback.expectDoneSyncWithin(1 second); info("async callback called")
verify(exchange, never()).setResponse(any[Message]); info("no response forwarded to exchange") verify(exchange, never()).setResponse(any[CamelMessage]); info("no response forwarded to exchange")
} }
} }
@ -189,15 +189,15 @@ class ActorProducerTest extends TestKit(ActorSystem("test")) with WordSpec with
"get async callback" in { "get async callback" in {
producer = given(outCapable = false, autoAck = false) producer = given(outCapable = false, autoAck = false)
val doneSync = producer.process(exchange, asyncCallback) val doneSync = producer.processExchangeAdapter(exchange, asyncCallback)
doneSync must be(false) doneSync must be(false)
within(1 second) { within(1 second) {
probe.expectMsgType[Message]; info("message sent to consumer") probe.expectMsgType[CamelMessage]; info("message sent to consumer")
probe.sender ! Ack probe.sender ! Ack
asyncCallback.expectDoneAsyncWithin(remaining); info("async callback called") asyncCallback.expectDoneAsyncWithin(remaining); info("async callback called")
} }
verify(exchange, never()).setResponse(any[Message]); info("no response forwarded to exchange") verify(exchange, never()).setResponse(any[CamelMessage]); info("no response forwarded to exchange")
} }
} }
@ -205,14 +205,14 @@ class ActorProducerTest extends TestKit(ActorSystem("test")) with WordSpec with
"fail" in { "fail" in {
producer = given(outCapable = false, autoAck = false) producer = given(outCapable = false, autoAck = false)
producer.process(exchange, asyncCallback) producer.processExchangeAdapter(exchange, asyncCallback)
within(1 second) { within(1 second) {
probe.expectMsgType[Message]; info("message sent to consumer") probe.expectMsgType[CamelMessage]; info("message sent to consumer")
probe.sender ! "some neither Ack nor Failure response" probe.sender ! "some neither Ack nor Failure response"
asyncCallback.expectDoneAsyncWithin(remaining); info("async callback called") asyncCallback.expectDoneAsyncWithin(remaining); info("async callback called")
} }
verify(exchange, never()).setResponse(any[Message]); info("no response forwarded to exchange") verify(exchange, never()).setResponse(any[CamelMessage]); info("no response forwarded to exchange")
verify(exchange).setFailure(any[Failure]); info("failure set") verify(exchange).setFailure(any[Failure]); info("failure set")
} }
@ -222,7 +222,7 @@ class ActorProducerTest extends TestKit(ActorSystem("test")) with WordSpec with
"set failure on exchange" in { "set failure on exchange" in {
producer = given(outCapable = false, replyTimeout = 10 millis, autoAck = false) producer = given(outCapable = false, replyTimeout = 10 millis, autoAck = false)
producer.process(exchange, asyncCallback) producer.processExchangeAdapter(exchange, asyncCallback)
asyncCallback.awaitCalled(100 millis) asyncCallback.awaitCalled(100 millis)
verify(exchange).setFailure(any[Failure]) verify(exchange).setFailure(any[Failure])
@ -233,27 +233,22 @@ class ActorProducerTest extends TestKit(ActorSystem("test")) with WordSpec with
"set an exception on exchange" in { "set an exception on exchange" in {
producer = given(outCapable = false, autoAck = false) producer = given(outCapable = false, autoAck = false)
val doneSync = producer.process(exchange, asyncCallback) val doneSync = producer.processExchangeAdapter(exchange, asyncCallback)
doneSync must be(false) doneSync must be(false)
within(1 second) { within(1 second) {
probe.expectMsgType[Message]; info("message sent to consumer") probe.expectMsgType[CamelMessage]; info("message sent to consumer")
probe.sender ! Failure(new Exception) probe.sender ! Failure(new Exception)
asyncCallback.awaitCalled(remaining); asyncCallback.awaitCalled(remaining);
} }
verify(exchange, never()).setResponse(any[Message]); info("no response forwarded to exchange") verify(exchange, never()).setResponse(any[CamelMessage]); info("no response forwarded to exchange")
verify(exchange).setFailure(any[Failure]); info("failure set") verify(exchange).setFailure(any[Failure]); info("failure set")
} }
} }
} }
} }
} }
} }
} }
trait ActorProducerFixture extends MockitoSugar with BeforeAndAfterAll with BeforeAndAfterEach { self: TestKit with MustMatchers with Suite trait ActorProducerFixture extends MockitoSugar with BeforeAndAfterAll with BeforeAndAfterEach { self: TestKit with MustMatchers with Suite
@ -261,11 +256,12 @@ trait ActorProducerFixture extends MockitoSugar with BeforeAndAfterAll with Befo
var exchange: CamelExchangeAdapter = _ var exchange: CamelExchangeAdapter = _
var callback: AsyncCallback = _ var callback: AsyncCallback = _
var producer: ConsumerAsyncProcessor = _ var producer: ActorProducer = _
var message: Message = _ var message: CamelMessage = _
var probe: TestProbe = _ var probe: TestProbe = _
var asyncCallback: TestAsyncCallback = _ var asyncCallback: TestAsyncCallback = _
var actorEndpointPath: ActorEndpointPath = _ var actorEndpointPath: ActorEndpointPath = _
var actorComponent: ActorComponent = _
override protected def beforeEach() { override protected def beforeEach() {
asyncCallback = createAsyncCallback asyncCallback = createAsyncCallback
@ -275,20 +271,20 @@ trait ActorProducerFixture extends MockitoSugar with BeforeAndAfterAll with Befo
exchange = mock[CamelExchangeAdapter] exchange = mock[CamelExchangeAdapter]
callback = mock[AsyncCallback] callback = mock[AsyncCallback]
actorEndpointPath = mock[ActorEndpointPath] actorEndpointPath = mock[ActorEndpointPath]
actorComponent = mock[ActorComponent]
producer = new ConsumerAsyncProcessor(config(), camel) producer = new ActorProducer(config(), camel)
message = Message(null, null) message = CamelMessage(null, null)
} }
override protected def afterAll() { override protected def afterAll() {
system.shutdown() system.shutdown()
} }
def msg(s: String) = Message(s, Map.empty) 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) = { def given(actor: ActorRef = probe.ref, outCapable: Boolean = true, autoAck: Boolean = true, replyTimeout: Duration = Int.MaxValue seconds) = {
prepareMocks(actor, outCapable = outCapable) prepareMocks(actor, outCapable = outCapable)
new ConsumerAsyncProcessor(config(isAutoAck = autoAck, _replyTimeout = replyTimeout), camel) new ActorProducer(config(isAutoAck = autoAck, _replyTimeout = replyTimeout), camel)
} }
def createAsyncCallback = new TestAsyncCallback def createAsyncCallback = new TestAsyncCallback
@ -323,15 +319,13 @@ trait ActorProducerFixture extends MockitoSugar with BeforeAndAfterAll with Befo
} }
def config(endpointUri: String = "test-uri", isAutoAck: Boolean = true, _replyTimeout: Duration = Int.MaxValue seconds) = { def config(endpointUri: String = "test-uri", isAutoAck: Boolean = true, _replyTimeout: Duration = Int.MaxValue seconds) = {
new ActorEndpointConfig { val endpoint = new ActorEndpoint(endpointUri, actorComponent, actorEndpointPath, camel)
val path = actorEndpointPath endpoint.autoack = isAutoAck
val getEndpointUri = endpointUri endpoint.replyTimeout = _replyTimeout
autoack = isAutoAck endpoint
replyTimeout = _replyTimeout
}
} }
def prepareMocks(actor: ActorRef, message: Message = message, outCapable: Boolean) { def prepareMocks(actor: ActorRef, message: CamelMessage = message, outCapable: Boolean) {
when(actorEndpointPath.findActorIn(any[ActorSystem])) thenReturn Option(actor) when(actorEndpointPath.findActorIn(any[ActorSystem])) thenReturn Option(actor)
when(exchange.toRequestMessage(any[Map[String, Any]])) thenReturn message when(exchange.toRequestMessage(any[Map[String, Any]])) thenReturn message
when(exchange.isOutCapable) thenReturn outCapable when(exchange.isOutCapable) thenReturn outCapable