diff --git a/akka-actor/src/main/scala/akka/actor/ActorCell.scala b/akka-actor/src/main/scala/akka/actor/ActorCell.scala index 3b068d5c2b..d577688526 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorCell.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorCell.scala @@ -555,13 +555,15 @@ private[akka] class ActorCell( def resume(): Unit = if (isNormal) dispatcher resume this def link(subject: ActorRef): Unit = if (!isTerminating) { - system.deathWatch.subscribe(self, subject) - if (system.settings.DebugLifecycle) system.eventStream.publish(Debug(self.path.toString, clazz(actor), "now monitoring " + subject)) + if (system.deathWatch.subscribe(self, subject)) { + if (system.settings.DebugLifecycle) system.eventStream.publish(Debug(self.path.toString, clazz(actor), "now monitoring " + subject)) + } } def unlink(subject: ActorRef): Unit = if (!isTerminating) { - system.deathWatch.unsubscribe(self, subject) - if (system.settings.DebugLifecycle) system.eventStream.publish(Debug(self.path.toString, clazz(actor), "stopped monitoring " + subject)) + if (system.deathWatch.unsubscribe(self, subject)) { + if (system.settings.DebugLifecycle) system.eventStream.publish(Debug(self.path.toString, clazz(actor), "stopped monitoring " + subject)) + } } def terminate() { @@ -691,7 +693,7 @@ private[akka] class ActorCell( parent.sendSystemMessage(ChildTerminated(self)) system.deathWatch.publish(Terminated(self)) if (system.settings.DebugLifecycle) - system.eventStream.publish(Debug(self.path.toString, clazz(actor), "stopped")) // FIXME: can actor be null? + system.eventStream.publish(Debug(self.path.toString, clazz(actor), "stopped")) } finally { if (a ne null) a.clearBehaviorStack() clearActorFields() diff --git a/akka-actor/src/main/scala/akka/actor/ActorRef.scala b/akka-actor/src/main/scala/akka/actor/ActorRef.scala index 668731e4bb..87534be5d9 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRef.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRef.scala @@ -383,15 +383,6 @@ private[akka] trait MinimalActorRef extends InternalActorRef with LocalRef { protected def writeReplace(): AnyRef = SerializedActorRef(path) } -private[akka] object MinimalActorRef { - def apply(_path: ActorPath, _provider: ActorRefProvider)(receive: PartialFunction[Any, Unit]): ActorRef = new MinimalActorRef { - def path = _path - def provider = _provider - override def !(message: Any)(implicit sender: ActorRef = null): Unit = - if (receive.isDefinedAt(message)) receive(message) - } -} - case class DeadLetter(message: Any, sender: ActorRef, recipient: ActorRef) private[akka] object DeadLetterActorRef { diff --git a/akka-camel-typed/src/main/java/akka/camel/consume.java b/akka-camel-typed/src/main/java/akka/camel/consume.java deleted file mode 100644 index cac399b162..0000000000 --- a/akka-camel-typed/src/main/java/akka/camel/consume.java +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright (C) 2009-2010 Typesafe Inc. - */ - -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 routeDefinitionHandler() - default RouteDefinitionIdentity.class; - -} diff --git a/akka-camel-typed/src/main/resources/META-INF/services/org/apache/camel/component/typed-actor b/akka-camel-typed/src/main/resources/META-INF/services/org/apache/camel/component/typed-actor deleted file mode 100644 index 02efe457e6..0000000000 --- a/akka-camel-typed/src/main/resources/META-INF/services/org/apache/camel/component/typed-actor +++ /dev/null @@ -1 +0,0 @@ -class=akka.camel.component.TypedActorComponent \ No newline at end of file diff --git a/akka-camel-typed/src/main/scala/akka/camel/TypedCamel.scala b/akka-camel-typed/src/main/scala/akka/camel/TypedCamel.scala deleted file mode 100644 index e2a6c0b35a..0000000000 --- a/akka-camel-typed/src/main/scala/akka/camel/TypedCamel.scala +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright (C) 2009-2012 Typesafe Inc. - */ - -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 TypedActorComponent to context. - */ - def onCamelContextInit(context: CamelContext) { - context.addComponent(TypedActorComponent.InternalSchema, new TypedActorComponent) - } - - /** - * Configures a TypedConsumerPublishRequestor and a TypedConsumerPublisher - * and re-uses the activationTracker of service. - */ - 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 TypedConsumerPublishRequestor and - * TypedConsumerPublisher. - */ - def onCamelServiceStop(service: CamelService) { - unregisterPublishRequestor - consumerPublisher.stop - } - - private def registerPublishRequestor: Unit = registry.addListener(publishRequestor) - private def unregisterPublishRequestor: Unit = registry.removeListener(publishRequestor) -} diff --git a/akka-camel-typed/src/main/scala/akka/camel/TypedConsumer.scala b/akka-camel-typed/src/main/scala/akka/camel/TypedConsumer.scala deleted file mode 100644 index c7b8059153..0000000000 --- a/akka-camel-typed/src/main/scala/akka/camel/TypedConsumer.scala +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright (C) 2009-2012 Typesafe Inc. - */ - -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 f to actorRef if actorRef - * references a typed consumer actor. A valid reference to a typed consumer actor is a - * local actor reference with a target actor that implements TypedActor and - * has at least one of its methods annotated with @consume (on interface or - * implementation class). For each @consume-annotated method, f - * is called with the corresponding method 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) - } - } -} diff --git a/akka-camel-typed/src/main/scala/akka/camel/TypedConsumerPublisher.scala b/akka-camel-typed/src/main/scala/akka/camel/TypedConsumerPublisher.scala deleted file mode 100644 index 6822665bb9..0000000000 --- a/akka-camel-typed/src/main/scala/akka/camel/TypedConsumerPublisher.scala +++ /dev/null @@ -1,139 +0,0 @@ -/** - * Copyright (C) 2009-2012 Typesafe Inc. - */ - -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 - * TypedActorRegistered events and unpublication of typed consumer actor methods on - * TypedActorUnregistered 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 ConsumerMethodRegistered events and - * unpublishes a typed consumer actor method on ConsumerMethodUnregistered events. - * Publications are tracked by sending an activationTracker an EndpointActivated - * event, unpublications are tracked by sending an EndpointActivated 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 @consume 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 @consume 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 actorRef 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 actorRef 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) - } - } -} diff --git a/akka-camel-typed/src/main/scala/akka/camel/component/TypedActorComponent.scala b/akka-camel-typed/src/main/scala/akka/camel/component/TypedActorComponent.scala deleted file mode 100644 index b446a1fab1..0000000000 --- a/akka-camel-typed/src/main/scala/akka/camel/component/TypedActorComponent.scala +++ /dev/null @@ -1,85 +0,0 @@ -/** - * Copyright (C) 2009-2010 Typesafe Inc. - */ - -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 Actor.registry if the - * schema is TypedActorComponent.InternalSchema. If the schema - * name is typed-actor 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 org.apache.camel.component.bean.BeanEndpoint with a custom - * bean holder that uses Actor.registry 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 -} - -/** - * org.apache.camel.component.bean.BeanHolder implementation that uses - * Actor.registry for getting access to typed actors. - * - * @author Martin Krasser - */ -class TypedActorHolder(uri: String, context: CamelContext, name: String) - extends RegistryBean(context, name) { - - /** - * Returns an akka.camel.component.BeanInfo instance. - */ - override def getBeanInfo: BeanInfo = - new BeanInfo(getContext, getBean.getClass, getParameterMappingStrategy) - - /** - * Obtains a typed actor from Actor.registry if the schema is - * TypedActorComponent.InternalSchema. If the schema name is - * typed-actor this method obtains the typed actor from the - * CamelContext's registry. - * - * @return a typed actor or null. - */ - override def getBean: AnyRef = { - val internal = uri.startsWith(TypedActorComponent.InternalSchema) - if (internal) Actor.registry.local.typedActorFor(uuidFrom(getName)) getOrElse null else super.getBean - } -} diff --git a/akka-camel-typed/src/test/java/akka/camel/SampleErrorHandlingTypedConsumer.java b/akka-camel-typed/src/test/java/akka/camel/SampleErrorHandlingTypedConsumer.java deleted file mode 100644 index d8a8c79440..0000000000 --- a/akka-camel-typed/src/test/java/akka/camel/SampleErrorHandlingTypedConsumer.java +++ /dev/null @@ -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); - -} diff --git a/akka-camel-typed/src/test/java/akka/camel/SampleErrorHandlingTypedConsumerImpl.java b/akka-camel-typed/src/test/java/akka/camel/SampleErrorHandlingTypedConsumerImpl.java deleted file mode 100644 index 89b3948b00..0000000000 --- a/akka-camel-typed/src/test/java/akka/camel/SampleErrorHandlingTypedConsumerImpl.java +++ /dev/null @@ -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)); - } - -} diff --git a/akka-camel-typed/src/test/java/akka/camel/SampleRemoteTypedConsumer.java b/akka-camel-typed/src/test/java/akka/camel/SampleRemoteTypedConsumer.java deleted file mode 100644 index 41a3c3f057..0000000000 --- a/akka-camel-typed/src/test/java/akka/camel/SampleRemoteTypedConsumer.java +++ /dev/null @@ -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); -} diff --git a/akka-camel-typed/src/test/java/akka/camel/SampleRemoteTypedConsumerImpl.java b/akka-camel-typed/src/test/java/akka/camel/SampleRemoteTypedConsumerImpl.java deleted file mode 100644 index 067fb4eda6..0000000000 --- a/akka-camel-typed/src/test/java/akka/camel/SampleRemoteTypedConsumerImpl.java +++ /dev/null @@ -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); - } - -} diff --git a/akka-camel-typed/src/test/java/akka/camel/SampleRouteDefinitionHandler.java b/akka-camel-typed/src/test/java/akka/camel/SampleRouteDefinitionHandler.java deleted file mode 100644 index f1a99aa7d4..0000000000 --- a/akka-camel-typed/src/test/java/akka/camel/SampleRouteDefinitionHandler.java +++ /dev/null @@ -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(); - } -} diff --git a/akka-camel-typed/src/test/java/akka/camel/SampleTypedActor.java b/akka-camel-typed/src/test/java/akka/camel/SampleTypedActor.java deleted file mode 100644 index 798d07a66c..0000000000 --- a/akka-camel-typed/src/test/java/akka/camel/SampleTypedActor.java +++ /dev/null @@ -1,9 +0,0 @@ -package akka.camel; - -/** - * @author Martin Krasser - */ -public interface SampleTypedActor { - - public String foo(String s); -} diff --git a/akka-camel-typed/src/test/java/akka/camel/SampleTypedActorImpl.java b/akka-camel-typed/src/test/java/akka/camel/SampleTypedActorImpl.java deleted file mode 100644 index 93d6cd9395..0000000000 --- a/akka-camel-typed/src/test/java/akka/camel/SampleTypedActorImpl.java +++ /dev/null @@ -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); - } - -} diff --git a/akka-camel-typed/src/test/java/akka/camel/SampleTypedConsumer.java b/akka-camel-typed/src/test/java/akka/camel/SampleTypedConsumer.java deleted file mode 100644 index 26283d8e61..0000000000 --- a/akka-camel-typed/src/test/java/akka/camel/SampleTypedConsumer.java +++ /dev/null @@ -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); -} diff --git a/akka-camel-typed/src/test/java/akka/camel/SampleTypedConsumerImpl.java b/akka-camel-typed/src/test/java/akka/camel/SampleTypedConsumerImpl.java deleted file mode 100644 index 8a402133f6..0000000000 --- a/akka-camel-typed/src/test/java/akka/camel/SampleTypedConsumerImpl.java +++ /dev/null @@ -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) { - } -} diff --git a/akka-camel-typed/src/test/java/akka/camel/SampleTypedSingleConsumer.java b/akka-camel-typed/src/test/java/akka/camel/SampleTypedSingleConsumer.java deleted file mode 100644 index ff0b7bc715..0000000000 --- a/akka-camel-typed/src/test/java/akka/camel/SampleTypedSingleConsumer.java +++ /dev/null @@ -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); - -} diff --git a/akka-camel-typed/src/test/java/akka/camel/SampleTypedSingleConsumerImpl.java b/akka-camel-typed/src/test/java/akka/camel/SampleTypedSingleConsumerImpl.java deleted file mode 100644 index fa4807eec4..0000000000 --- a/akka-camel-typed/src/test/java/akka/camel/SampleTypedSingleConsumerImpl.java +++ /dev/null @@ -1,11 +0,0 @@ -package akka.camel; - -/** - * @author Martin Krasser - */ -public class SampleTypedSingleConsumerImpl implements SampleTypedSingleConsumer { - - public void foo(String b) { - } - -} diff --git a/akka-camel-typed/src/test/java/akka/camel/TypedConsumerJavaTestBase.java b/akka-camel-typed/src/test/java/akka/camel/TypedConsumerJavaTestBase.java deleted file mode 100644 index d6ea1da9f4..0000000000 --- a/akka-camel-typed/src/test/java/akka/camel/TypedConsumerJavaTestBase.java +++ /dev/null @@ -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); - } -} diff --git a/akka-camel-typed/src/test/scala/akka/camel/TypedCamelTestSupport.scala b/akka-camel-typed/src/test/scala/akka/camel/TypedCamelTestSupport.scala deleted file mode 100644 index 42b38e5bb3..0000000000 --- a/akka-camel-typed/src/test/scala/akka/camel/TypedCamelTestSupport.scala +++ /dev/null @@ -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) - } -} - diff --git a/akka-camel-typed/src/test/scala/akka/camel/TypedConsumerJavaTest.scala b/akka-camel-typed/src/test/scala/akka/camel/TypedConsumerJavaTest.scala deleted file mode 100644 index d887a378ad..0000000000 --- a/akka-camel-typed/src/test/scala/akka/camel/TypedConsumerJavaTest.scala +++ /dev/null @@ -1,5 +0,0 @@ -package akka.camel - -import org.scalatest.junit.JUnitSuite - -class TypedConsumerJavaTest extends TypedConsumerJavaTestBase with JUnitSuite diff --git a/akka-camel-typed/src/test/scala/akka/camel/TypedConsumerPublishRequestorTest.scala b/akka-camel-typed/src/test/scala/akka/camel/TypedConsumerPublishRequestorTest.scala deleted file mode 100644 index 322a0412c5..0000000000 --- a/akka-camel-typed/src/test/scala/akka/camel/TypedConsumerPublishRequestorTest.scala +++ /dev/null @@ -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 - } -} - diff --git a/akka-camel-typed/src/test/scala/akka/camel/TypedConsumerScalaTest.scala b/akka-camel-typed/src/test/scala/akka/camel/TypedConsumerScalaTest.scala deleted file mode 100644 index 20703bdffe..0000000000 --- a/akka-camel-typed/src/test/scala/akka/camel/TypedConsumerScalaTest.scala +++ /dev/null @@ -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 - } -} diff --git a/akka-camel-typed/src/test/scala/akka/camel/component/TypedActorComponentFeatureTest.scala b/akka-camel-typed/src/test/scala/akka/camel/component/TypedActorComponentFeatureTest.scala deleted file mode 100644 index d1084f2719..0000000000 --- a/akka-camel-typed/src/test/scala/akka/camel/component/TypedActorComponentFeatureTest.scala +++ /dev/null @@ -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") - } - } -} diff --git a/akka-camel/src/main/resources/META-INF/services/org/apache/camel/component/actor b/akka-camel/src/main/resources/META-INF/services/org/apache/camel/component/actor deleted file mode 100644 index 386928c5a8..0000000000 --- a/akka-camel/src/main/resources/META-INF/services/org/apache/camel/component/actor +++ /dev/null @@ -1 +0,0 @@ -class=akka.camel.component.ActorComponent \ No newline at end of file diff --git a/akka-camel/src/main/scala/akka/camel/Activation.scala b/akka-camel/src/main/scala/akka/camel/Activation.scala new file mode 100644 index 0000000000..cf07a19171 --- /dev/null +++ b/akka-camel/src/main/scala/akka/camel/Activation.scala @@ -0,0 +1,94 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + +package akka.camel + +import internal._ +import akka.util.{ Timeout, Duration } +import akka.dispatch.Future +import java.util.concurrent.TimeoutException +import akka.actor.{ ActorSystem, Props, ActorRef } +import akka.pattern._ + +/** + * Activation trait that can be used to wait on activation or de-activation of Camel endpoints. + * The Camel endpoints are activated asynchronously. This trait can signal when an endpoint is activated or de-activated. + */ +trait Activation { + import akka.dispatch.Await + + def system: ActorSystem + + private val activationTracker = system.actorOf(Props[ActivationTracker]) + + /** + * Awaits for endpoint to be activated. It blocks until the endpoint is registered in camel context or timeout expires. + * @param endpoint the endpoint to wait for to be activated + * @param timeout the timeout for the wait + * @throws akka.camel.ActivationTimeoutException if endpoint is not activated within timeout. + */ + def awaitActivation(endpoint: ActorRef, timeout: Duration): ActorRef = { + try { + Await.result(activationFutureFor(endpoint, timeout), timeout) + } catch { + case e: TimeoutException ⇒ throw new ActivationTimeoutException(endpoint, timeout) + } + } + + /** + * Awaits for endpoint to be de-activated. It is blocking until endpoint is unregistered in camel context or timeout expires. + * @param endpoint the endpoint to wait for to be de-activated + * @param timeout the timeout for the wait + * @throws akka.camel.DeActivationTimeoutException if endpoint is not de-activated within timeout. + */ + def awaitDeactivation(endpoint: ActorRef, timeout: Duration) { + try { + Await.result(deactivationFutureFor(endpoint, timeout), timeout) + } catch { + case e: TimeoutException ⇒ throw new DeActivationTimeoutException(endpoint, timeout) + } + } + + /** + * Similar to `awaitActivation` but returns a future instead. + * @param endpoint the endpoint to be activated + * @param timeout the timeout for the Future + */ + def activationFutureFor(endpoint: ActorRef, timeout: Duration): Future[ActorRef] = { + (activationTracker.ask(AwaitActivation(endpoint))(Timeout(timeout))).map[ActorRef] { + case EndpointActivated(_) ⇒ endpoint + case EndpointFailedToActivate(_, cause) ⇒ throw cause + } + } + + /** + * Similar to awaitDeactivation but returns a future instead. + * @param endpoint the endpoint to be deactivated + * @param timeout the timeout of the Future + */ + def deactivationFutureFor(endpoint: ActorRef, timeout: Duration): Future[Unit] = { + (activationTracker.ask(AwaitDeActivation(endpoint))(Timeout(timeout))).map[Unit] { + case EndpointDeActivated(_) ⇒ () + case EndpointFailedToDeActivate(_, cause) ⇒ throw cause + } + } +} + +/** + * An exception for when a timeout has occurred during deactivation of an endpoint + * @param endpoint the endpoint that could not be de-activated in time + * @param timeout the timeout + */ +class DeActivationTimeoutException(endpoint: ActorRef, timeout: Duration) extends TimeoutException { + override def getMessage = "Timed out after %s, while waiting for de-activation of %s" format (timeout, endpoint.path) +} + +/** + * An exception for when a timeout has occurred during the activation of an endpoint + * @param endpoint the endpoint that could not be activated in time + * @param timeout the timeout + */ +class ActivationTimeoutException(endpoint: ActorRef, timeout: Duration) extends TimeoutException { + override def getMessage = "Timed out after %s, while waiting for activation of %s" format (timeout, endpoint.path) +} \ No newline at end of file diff --git a/akka-camel/src/main/scala/akka/camel/ActorRouteDefinition.scala b/akka-camel/src/main/scala/akka/camel/ActorRouteDefinition.scala new file mode 100644 index 0000000000..3f7905462c --- /dev/null +++ b/akka-camel/src/main/scala/akka/camel/ActorRouteDefinition.scala @@ -0,0 +1,15 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + +package akka.camel + +import internal.component.ActorEndpointPath +import akka.actor.ActorRef +import org.apache.camel.model.ProcessorDefinition + +class ActorRouteDefinition(definition: ProcessorDefinition[_]) { + def to(actorRef: ActorRef) = definition.to(ActorEndpointPath(actorRef).toCamelPath()) + def to(actorRef: ActorRef, consumerConfig: ConsumerConfig) = definition.to(ActorEndpointPath(actorRef).toCamelPath(consumerConfig)) +} + diff --git a/akka-camel/src/main/scala/akka/camel/Camel.scala b/akka-camel/src/main/scala/akka/camel/Camel.scala new file mode 100644 index 0000000000..085aad5316 --- /dev/null +++ b/akka-camel/src/main/scala/akka/camel/Camel.scala @@ -0,0 +1,63 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + +package akka.camel + +import internal._ +import akka.actor._ +import org.apache.camel.{ ProducerTemplate, CamelContext } + +//TODO complete this doc +/** + * Camel trait encapsulates the underlying camel machinery. + * '''Note:''' `CamelContext` and `ProducerTemplate` are stopped when the associated actor system is shut down. + * + */ +trait Camel extends ConsumerRegistry with ProducerRegistry with Extension with Activation { + /** + * Underlying camel context. + * + * It can be used to configure camel manually, i.e. when the user wants to add new routes or endpoints, + * i.e.
camel.context.addRoutes(...)
+ * + * @see [[org.apache.camel.CamelContext]] + */ + def context: CamelContext + + /** + * Producer template. + * @see [[org.apache.camel.ProducerTemplate]] + */ + def template: ProducerTemplate + +} + +/** + * This class can be used to get hold of an instance of the Camel class bound to the actor system. + *

For example: + * {{{ + * val system = ActorSystem("some system") + * val camel = CamelExtension(system) + * camel.context.addRoutes(...) + * }}} + * + * @see akka.actor.ExtensionId + * @see akka.actor.ExtensionIdProvider + * + */ +object CamelExtension extends ExtensionId[Camel] with ExtensionIdProvider { + + /** + * Creates a new instance of Camel and makes sure it gets stopped when the actor system is shutdown. + */ + def createExtension(system: ExtendedActorSystem) = { + val camel = new DefaultCamel(system).start + system.registerOnTermination(camel.shutdown()) + camel + } + + def lookup(): ExtensionId[Camel] = CamelExtension + + override def get(system: ActorSystem): Camel = super.get(system) +} \ No newline at end of file diff --git a/akka-camel/src/main/scala/akka/camel/CamelContextLifecycle.scala b/akka-camel/src/main/scala/akka/camel/CamelContextLifecycle.scala deleted file mode 100644 index 468a7c8ce7..0000000000 --- a/akka-camel/src/main/scala/akka/camel/CamelContextLifecycle.scala +++ /dev/null @@ -1,186 +0,0 @@ -/** - * Copyright (C) 2009-2010 Typesafe Inc. - */ - -package akka.camel - -import org.apache.camel.{ ProducerTemplate, CamelContext } -import org.apache.camel.impl.DefaultCamelContext - -import akka.event.EventHandler -import akka.japi.{ Option ⇒ JOption } - -import TypedCamelAccess._ - -/** - * Manages the lifecycle of a CamelContext. Allowed transitions are - * init -> start -> stop -> init -> ... etc. - * - * @author Martin Krasser - */ -trait CamelContextLifecycle { - // TODO: enforce correct state transitions - // valid: init -> start -> stop -> init ... - - private var _context: Option[CamelContext] = None - private var _template: Option[ProducerTemplate] = None - - private var _initialized = false - private var _started = false - - /** - * Returns Some(CamelContext) (containing the current CamelContext) - * if CamelContextLifecycle has been initialized, otherwise None. - */ - def context: Option[CamelContext] = _context - - /** - * Returns Some(ProducerTemplate) (containing the current ProducerTemplate) - * if CamelContextLifecycle has been initialized, otherwise None. - */ - def template: Option[ProducerTemplate] = _template - - /** - * Returns Some(CamelContext) (containing the current CamelContext) - * if CamelContextLifecycle has been initialized, otherwise None. - *

- * Java API. - */ - def getContext: JOption[CamelContext] = context - - /** - * Returns Some(ProducerTemplate) (containing the current ProducerTemplate) - * if CamelContextLifecycle has been initialized, otherwise None. - *

- * Java API. - */ - def getTemplate: JOption[ProducerTemplate] = template - - /** - * Returns the current CamelContext if this CamelContextLifecycle - * has been initialized, otherwise throws an IllegalStateException. - */ - def mandatoryContext = - if (context.isDefined) context.get - else throw new IllegalStateException("no current CamelContext") - - /** - * Returns the current ProducerTemplate if this CamelContextLifecycle - * has been initialized, otherwise throws an IllegalStateException. - */ - def mandatoryTemplate = - if (template.isDefined) template.get - else throw new IllegalStateException("no current ProducerTemplate") - - /** - * Returns the current CamelContext if this CamelContextLifecycle - * has been initialized, otherwise throws an IllegalStateException. - *

- * Java API. - */ - def getMandatoryContext = mandatoryContext - - /** - * Returns the current ProducerTemplate if this CamelContextLifecycle - * has been initialized, otherwise throws an IllegalStateException. - *

- * Java API. - */ - def getMandatoryTemplate = mandatoryTemplate - - def initialized = _initialized - def started = _started - - /** - * Starts the CamelContext and an associated ProducerTemplate. - */ - def start = { - for { - c ← context - t ← template - } { - c.start() - t.start() - _started = true - EventHandler.info(this, "Camel context started") - } - } - - /** - * Stops the CamelContext and the associated ProducerTemplate. - */ - def stop = { - for { - t ← template - c ← context - } { - t.stop - c.stop - _started = false - _initialized = false - EventHandler.info(this, "Camel context stopped") - } - } - - /** - * Initializes this instance a new DefaultCamelContext. - */ - def init(): Unit = init(new DefaultCamelContext) - - /** - * Initializes this instance with the given CamelContext. For the passed context - * stream-caching is enabled. If applications want to disable stream-caching they can do so - * after this method returned and prior to calling start. - */ - def init(context: CamelContext) { - context.setStreamCaching(true) - - for (tc ← TypedCamelModule.typedCamelObject) tc.onCamelContextInit(context) - - this._context = Some(context) - this._template = Some(context.createProducerTemplate) - - _initialized = true - EventHandler.info(this, "Camel context initialized") - } -} - -/** - * Manages a global CamelContext and an associated ProducerTemplate. - */ -object CamelContextManager extends CamelContextLifecycle { - - // ----------------------------------------------------- - // The inherited getters aren't statically accessible - // from Java. Therefore, they are redefined here. - // TODO: investigate if this is a Scala bug. - // ----------------------------------------------------- - - /** - * see CamelContextLifecycle.getContext - *

- * Java API. - */ - override def getContext: JOption[CamelContext] = super.getContext - - /** - * see CamelContextLifecycle.getTemplate - *

- * Java API. - */ - override def getTemplate: JOption[ProducerTemplate] = super.getTemplate - - /** - * see CamelContextLifecycle.getMandatoryContext - *

- * Java API. - */ - override def getMandatoryContext = super.getMandatoryContext - - /** - * see CamelContextLifecycle.getMandatoryTemplate - *

- * Java API. - */ - override def getMandatoryTemplate = super.getMandatoryTemplate -} diff --git a/akka-camel/src/main/scala/akka/camel/CamelMessage.scala b/akka-camel/src/main/scala/akka/camel/CamelMessage.scala new file mode 100644 index 0000000000..afc3d885d8 --- /dev/null +++ b/akka-camel/src/main/scala/akka/camel/CamelMessage.scala @@ -0,0 +1,266 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + +package akka.camel + +import java.util.{ Map ⇒ JMap, Set ⇒ JSet } + +import scala.collection.JavaConversions._ + +import akka.japi.{ Function ⇒ JFunction } +import org.apache.camel.{ CamelContext, Message ⇒ JCamelMessage } + +/** + * An immutable representation of a Camel message. + * + * @author Martin Krasser + */ +case class CamelMessage(body: Any, headers: Map[String, Any]) { + + def this(body: Any, headers: JMap[String, Any]) = this(body, headers.toMap) //for Java + + override def toString = "CamelMessage(%s, %s)" format (body, headers) + + /** + * Returns those headers from this message whose name is contained in names. + */ + def headers(names: Set[String]): Map[String, Any] = headers.filterKeys(names contains _) + + /** + * Returns those headers from this message whose name is contained in names. + * The returned headers map is backed up by an immutable headers map. Any attempt to modify + * the returned map will throw an exception. + *

+ * Java API + */ + def getHeaders(names: JSet[String]): JMap[String, Any] = headers(names.toSet) + + /** + * Returns all headers from this message. The returned headers map is backed up by this + * message's immutable headers map. Any attempt to modify the returned map will throw an + * exception. + *

+ * Java API + */ + def getHeaders: JMap[String, Any] = headers + + /** + * Returns the header with given name. Throws NoSuchElementException + * if the header doesn't exist. + */ + def header(name: String): Option[Any] = headers.get(name) + + /** + * Returns the header with given name. Throws NoSuchElementException + * if the header doesn't exist. + *

+ * Java API + */ + def getHeader(name: String): Any = headers(name) + + /** + * Creates a CamelMessage with a transformed body using a transformer function. + */ + def mapBody[A, B](transformer: A ⇒ B): CamelMessage = withBody(transformer(body.asInstanceOf[A])) + + /** + * Creates a CamelMessage with a transformed body using a transformer function. + *

+ * Java API + */ + def mapBody[A, B](transformer: JFunction[A, B]): CamelMessage = withBody(transformer(body.asInstanceOf[A])) + + /** + * Creates a CamelMessage with a given body. + */ + def withBody(body: Any) = CamelMessage(body, this.headers) + + /** + * Creates a new CamelMessage with given headers. + */ + def withHeaders[A](headers: Map[String, A]): CamelMessage = copy(this.body, headers) + + /** + * Creates a new CamelMessage with given headers. A copy of the headers map is made. + *

+ * Java API + */ + def withHeaders[A](headers: JMap[String, A]): CamelMessage = withHeaders(headers.toMap) + + /** + * Creates a new CamelMessage with given headers added to the current headers. + */ + def addHeaders[A](headers: Map[String, A]): CamelMessage = copy(this.body, this.headers ++ headers) + + /** + * Creates a new CamelMessage with given headers added to the current headers. + * A copy of the headers map is made. + *

+ * Java API + */ + def addHeaders[A](headers: JMap[String, A]): CamelMessage = addHeaders(headers.toMap) + + /** + * Creates a new CamelMessage with the given header added to the current headers. + */ + def addHeader(header: (String, Any)): CamelMessage = copy(this.body, this.headers + header) + + /** + * Creates a new CamelMessage with the given header, represented by name and + * value added to the existing headers. + *

+ * Java API + */ + def addHeader(name: String, value: Any): CamelMessage = addHeader((name, value)) + + /** + * Creates a new CamelMessage where the header with given headerName is removed from + * the existing headers. + */ + def withoutHeader(headerName: String) = copy(this.body, this.headers - headerName) + + def copyContentTo(to: JCamelMessage) = { + to.setBody(this.body) + for ((name, value) ← this.headers) to.getHeaders.put(name, value.asInstanceOf[AnyRef]) + } + + /** + * Returns the body of the message converted to the type T. Conversion is done + * using Camel's type converter. The type converter is obtained from the CamelContext managed + * by CamelContextManager. Applications have to ensure proper initialization of + * CamelContextManager. + * + * @see CamelContextManager. + */ + + def bodyAs[T](implicit m: Manifest[T], camelContext: CamelContext): T = getBodyAs(m.erasure.asInstanceOf[Class[T]], camelContext) + + /** + * Returns the body of the message converted to the type as given by the clazz + * parameter. Conversion is done using Camel's type converter. The type converter is obtained + * from the CamelContext managed by CamelContextManager. Applications have to ensure proper + * initialization of CamelContextManager. + *

+ * Java API + * + * @see CamelContextManager. + */ + def getBodyAs[T](clazz: Class[T], camelContext: CamelContext): T = + camelContext.getTypeConverter.mandatoryConvertTo[T](clazz, body) + + /** + * Creates a CamelMessage with current body converted to type T. + */ + def withBodyAs[T](implicit m: Manifest[T], camelContext: CamelContext): CamelMessage = withBodyAs(m.erasure.asInstanceOf[Class[T]]) + + /** + * Creates a CamelMessage with current body converted to type clazz. + *

+ * Java API + */ + def withBodyAs[T](clazz: Class[T])(implicit camelContext: CamelContext): CamelMessage = withBody(getBodyAs(clazz, camelContext)) + + /** + * Returns the header with given name converted to type T. Throws + * NoSuchElementException if the header doesn't exist. + */ + def headerAs[T](name: String)(implicit m: Manifest[T], camelContext: CamelContext): Option[T] = header(name).map(camelContext.getTypeConverter.mandatoryConvertTo[T](m.erasure.asInstanceOf[Class[T]], _)) + + /** + * Returns the header with given name converted to type as given by the clazz + * parameter. Throws NoSuchElementException if the header doesn't exist. + *

+ * Java API + */ + def getHeaderAs[T](name: String, clazz: Class[T], camelContext: CamelContext) = headerAs[T](name)(Manifest.classType(clazz), camelContext).get + +} + +/** + * Companion object of CamelMessage class. + * + * @author Martin Krasser + */ +object CamelMessage { + + /** + * CamelMessage header to correlate request with response messages. Applications that send + * messages to a Producer actor may want to set this header on the request message + * so that it can be correlated with an asynchronous response. Messages send to Consumer + * actors have this header already set. + */ + val MessageExchangeId = "MessageExchangeId".intern + + /** + * Creates a canonical form of the given message msg. If msg of type + * CamelMessage then msg is returned, otherwise msg is set as body of a + * newly created CamelMessage object. + */ + def canonicalize(msg: Any) = msg match { + case mobj: CamelMessage ⇒ mobj + case body ⇒ CamelMessage(body, Map.empty) + } + + /** + * Creates a new CamelMessage object from the Camel message. + */ + def from(camelMessage: JCamelMessage): CamelMessage = from(camelMessage, Map.empty) + + /** + * Creates a new CamelMessage object from the Camel message. + * + * @param headers additional headers to set on the created CamelMessage in addition to those + * in the Camel message. + */ + def from(camelMessage: JCamelMessage, headers: Map[String, Any]): CamelMessage = CamelMessage(camelMessage.getBody, headers ++ camelMessage.getHeaders) + +} + +/** + * Positive acknowledgement message (used for application-acknowledged message receipts). + * + * @author Martin Krasser + */ +case object Ack { + /** Java API to get the Ack singleton */ + def ack = this +} + +/** + * An immutable representation of a failed Camel exchange. It contains the failure cause + * obtained from Exchange.getException and the headers from either the Exchange.getIn + * message or Exchange.getOut message, depending on the exchange pattern. + * + * @author Martin Krasser + */ +case class Failure(val cause: Throwable, val headers: Map[String, Any] = Map.empty) { + + /** + * Creates a Failure with cause body and empty headers map. + */ + def this(cause: Throwable) = this(cause, Map.empty[String, Any]) + + /** + * Creates a Failure with given cause and headers map. A copy of the headers map is made. + *

+ * Java API + */ + def this(cause: Throwable, headers: JMap[String, Any]) = this(cause, headers.toMap) + + /** + * Returns the cause of this Failure. + *

+ * Java API. + */ + def getCause = cause + + /** + * Returns all headers from this failure message. The returned headers map is backed up by + * this message's immutable headers map. Any attempt to modify the returned map will throw + * an exception. + *

+ * Java API + */ + def getHeaders: JMap[String, Any] = headers +} \ No newline at end of file diff --git a/akka-camel/src/main/scala/akka/camel/CamelService.scala b/akka-camel/src/main/scala/akka/camel/CamelService.scala deleted file mode 100644 index 0b8a2aece0..0000000000 --- a/akka-camel/src/main/scala/akka/camel/CamelService.scala +++ /dev/null @@ -1,268 +0,0 @@ -/** - * Copyright (C) 2009-2010 Typesafe Inc. - */ -package akka.camel - -import java.util.concurrent.CountDownLatch -import java.util.concurrent.TimeUnit - -import org.apache.camel.CamelContext - -import akka.actor.{ newUuid, Props, LocalActorRef, Actor } -import akka.config.Config._ -import akka.japi.{ SideEffect, Option ⇒ JOption } -import akka.util.Bootable - -import TypedCamelAccess._ -import akka.dispatch.Await - -/** - * Publishes consumer actors at their Camel endpoints. Consumer actors are published asynchronously when - * they are started and un-published asynchronously when they are stopped. The CamelService is notified - * about actor life cycle by registering listeners at Actor.registry. - *

- * If the akka-camel-typed jar is on the classpath, it is automatically detected by the CamelService. The - * optional akka-camel-typed jar provides support for typed consumer actors. - * - * @author Martin Krasser - */ -trait CamelService extends Bootable { - private[camel] val activationTracker = new LocalActorRef(Props[ActivationTracker], Props.randomName, true) - private[camel] val consumerPublisher = new LocalActorRef(Props(new ConsumerPublisher(activationTracker)), Props.randomName, true) - private[camel] val publishRequestor = new LocalActorRef(Props(new ConsumerPublishRequestor), Props.randomName, true) - - private val serviceEnabled = config.getList("akka.enabled-modules").exists(_ == "camel") - - /** - * Starts this CamelService if the akka.enabled-modules list contains "camel". - */ - abstract override def onLoad = { - super.onLoad - if (serviceEnabled) start - } - - /** - * Stops this CamelService if the akka.enabled-modules list contains "camel". - */ - abstract override def onUnload = { - if (serviceEnabled) stop - super.onUnload - } - - @deprecated("use start() instead", "1.1") - def load = start - - @deprecated("use stop() instead", "1.1") - def unload = stop - - /** - * Starts this CamelService. - */ - def start: CamelService = { - // Only init and start if not already done by system - if (!CamelContextManager.initialized) CamelContextManager.init - if (!CamelContextManager.started) CamelContextManager.start - - registerPublishRequestor - - // send registration events for all (un)typed actors that have been registered in the past. - for (event ← PublishRequestor.pastActorRegisteredEvents) publishRequestor ! event - - // init publishRequestor so that buffered and future events are delivered to consumerPublisher - publishRequestor ! InitPublishRequestor(consumerPublisher) - - for (tc ← TypedCamelModule.typedCamelObject) tc.onCamelServiceStart(this) - - // Register this instance as current CamelService and return it - CamelServiceManager.register(this) - CamelServiceManager.mandatoryService - } - - /** - * Stops this CamelService. - */ - def stop = { - // Unregister this instance as current CamelService - CamelServiceManager.unregister(this) - - for (tc ← TypedCamelModule.typedCamelObject) tc.onCamelServiceStop(this) - - // Remove related listeners from registry - unregisterPublishRequestor - - // Stop related services - consumerPublisher.stop - activationTracker.stop - CamelContextManager.stop - } - - /** - * Waits for an expected number (count) of consumer actor endpoints to be activated - * during execution of f. The wait-timeout is by default 10 seconds. Other timeout - * values can be set via the timeout and timeUnit parameters. - */ - def awaitEndpointActivation(count: Int, timeout: Long = 10, timeUnit: TimeUnit = TimeUnit.SECONDS)(f: ⇒ Unit): Boolean = { - val activation = expectEndpointActivationCount(count) - f; activation.await(timeout, timeUnit) - } - - /** - * Waits for an expected number (count) of consumer actor endpoints to be de-activated - * during execution of f. The wait-timeout is by default 10 seconds. Other timeout - * values can be set via the timeout and timeUnit - * parameters. - */ - def awaitEndpointDeactivation(count: Int, timeout: Long = 10, timeUnit: TimeUnit = TimeUnit.SECONDS)(f: ⇒ Unit): Boolean = { - val activation = expectEndpointDeactivationCount(count) - f; activation.await(timeout, timeUnit) - } - - /** - * Waits for an expected number (count) of consumer actor endpoints to be activated - * during execution of p. The wait timeout is 10 seconds. - *

- * Java API - */ - def awaitEndpointActivation(count: Int, p: SideEffect): Boolean = { - awaitEndpointActivation(count, 10, TimeUnit.SECONDS, p) - } - - /** - * Waits for an expected number (count) of consumer actor endpoints to be activated - * during execution of p. Timeout values can be set via the - * timeout and timeUnit parameters. - *

- * Java API - */ - def awaitEndpointActivation(count: Int, timeout: Long, timeUnit: TimeUnit, p: SideEffect): Boolean = { - awaitEndpointActivation(count, timeout, timeUnit) { p.apply } - } - - /** - * Waits for an expected number (count) of consumer actor endpoints to be de-activated - * during execution of p. The wait timeout is 10 seconds. - *

- * Java API - */ - def awaitEndpointDeactivation(count: Int, p: SideEffect): Boolean = { - awaitEndpointDeactivation(count, 10, TimeUnit.SECONDS, p) - } - - /** - * Waits for an expected number (count) of consumer actor endpoints to be de-activated - * during execution of p. Timeout values can be set via the - * timeout and timeUnit parameters. - *

- * Java API - */ - def awaitEndpointDeactivation(count: Int, timeout: Long, timeUnit: TimeUnit, p: SideEffect): Boolean = { - awaitEndpointDeactivation(count, timeout, timeUnit) { p.apply } - } - - /** - * Sets an expectation on the number of upcoming endpoint activations and returns - * a CountDownLatch that can be used to wait for the activations to occur. Endpoint - * activations that occurred in the past are not considered. - */ - private def expectEndpointActivationCount(count: Int): CountDownLatch = - Await.result((activationTracker ? SetExpectedActivationCount(count)).mapTo[CountDownLatch], 3 seconds) - - /** - * Sets an expectation on the number of upcoming endpoint de-activations and returns - * a CountDownLatch that can be used to wait for the de-activations to occur. Endpoint - * de-activations that occurred in the past are not considered. - */ - private def expectEndpointDeactivationCount(count: Int): CountDownLatch = - Await.result((activationTracker ? SetExpectedDeactivationCount(count)).mapTo[CountDownLatch], 3 seconds) - - private[camel] def registerPublishRequestor: Unit = - Actor.registry.addListener(publishRequestor) - - private[camel] def unregisterPublishRequestor: Unit = - Actor.registry.removeListener(publishRequestor) -} - -/** - * Manages a CamelService (the 'current' CamelService). - * - * @author Martin Krasser - */ -object CamelServiceManager { - - /** - * The current CamelService which is defined when a CamelService has been started. - */ - private var _current: Option[CamelService] = None - - /** - * Starts a new CamelService, makes it the current CamelService and returns it. - * - * @see CamelService#start - * @see CamelService#onLoad - */ - def startCamelService = CamelServiceFactory.createCamelService.start - - /** - * Stops the current CamelService (if defined). - * - * @see CamelService#stop - * @see CamelService#onUnload - */ - def stopCamelService = for (s ← service) s.stop - - /** - * Returns Some(CamelService) if this CamelService - * has been started, None otherwise. - */ - def service = _current - - /** - * Returns the current CamelService if CamelService - * has been started, otherwise throws an IllegalStateException. - *

- * Java API - */ - def getService: JOption[CamelService] = CamelServiceManager.service - - /** - * Returns Some(CamelService) (containing the current CamelService) - * if this CamelServicehas been started, None otherwise. - */ - def mandatoryService = - if (_current.isDefined) _current.get - else throw new IllegalStateException("co current CamelService") - - /** - * Returns Some(CamelService) (containing the current CamelService) - * if this CamelServicehas been started, None otherwise. - *

- * Java API - */ - def getMandatoryService = mandatoryService - - private[camel] def register(service: CamelService) = - if (_current.isDefined) throw new IllegalStateException("current CamelService already registered") - else _current = Some(service) - - private[camel] def unregister(service: CamelService) = - if (_current == Some(service)) _current = None - else throw new IllegalStateException("only current CamelService can be unregistered") -} - -/** - * @author Martin Krasser - */ -object CamelServiceFactory { - /** - * Creates a new CamelService instance. - */ - def createCamelService: CamelService = new CamelService {} - - /** - * Creates a new CamelService instance and initializes it with the given CamelContext. - */ - def createCamelService(camelContext: CamelContext): CamelService = { - CamelContextManager.init(camelContext) - createCamelService - } -} diff --git a/akka-camel/src/main/scala/akka/camel/Consumer.scala b/akka-camel/src/main/scala/akka/camel/Consumer.scala index 86f9d7f519..7c0119ef5e 100644 --- a/akka-camel/src/main/scala/akka/camel/Consumer.scala +++ b/akka-camel/src/main/scala/akka/camel/Consumer.scala @@ -1,144 +1,67 @@ /** - * Copyright (C) 2009-2010 Typesafe Inc. + * Copyright (C) 2009-2012 Typesafe Inc. */ package akka.camel +import internal.component.DurationTypeConverter import org.apache.camel.model.{ RouteDefinition, ProcessorDefinition } import akka.actor._ +import akka.util.Duration +import akka.util.duration._ /** * Mixed in by Actor implementations that consume message from Camel endpoints. * * @author Martin Krasser */ -trait Consumer { this: Actor ⇒ - import RouteDefinitionHandler._ +trait Consumer extends Actor with ConsumerConfig { - /** - * The default route definition handler is the identity function - */ - private[camel] var routeDefinitionHandler: RouteDefinitionHandler = identity - - /** - * Returns the Camel endpoint URI to consume messages from. - */ def endpointUri: String + protected[this] implicit def camel = CamelExtension(context.system) + protected[this] implicit def camelContext = camel.context + + camel.registerConsumer(endpointUri, this, activationTimeout) +} + +/** + * For internal use only. + */ +private[camel] object DefaultConsumerConfig extends ConsumerConfig + +trait ConsumerConfig { + /** - * Determines whether two-way communications between an endpoint and this consumer actor - * should be done in blocking or non-blocking mode (default is non-blocking). This method - * doesn't have any effect on one-way communications (they'll never block). + * How long the actor should wait for activation before it fails. */ - def blocking = false + def activationTimeout: Duration = 10 seconds + + /** + * When endpoint is out-capable (can produce responses) replyTimeout is the maximum time + * the endpoint can take to send the response before the message exchange fails. It defaults to 1 minute. + * This setting is used for out-capable, in-only, manually acknowledged communication. + * When the blocking is set to Blocking replyTimeout is ignored. + */ + def replyTimeout: Duration = 1 minute /** * Determines whether one-way communications between an endpoint and this consumer actor - * should be auto-acknowledged or system-acknowledged. + * should be auto-acknowledged or application-acknowledged. + * This flag has only effect when exchange is in-only. */ - def autoack = true + def autoack: Boolean = true /** - * Sets the route definition handler for creating a custom route to this consumer instance. + * The route definition handler for creating a custom route to this consumer instance. */ - def onRouteDefinition(h: RouteDefinition ⇒ ProcessorDefinition[_]): Unit = onRouteDefinition(from(h)) + //FIXME: write a test confirming onRouteDefinition gets called + def onRouteDefinition(rd: RouteDefinition): ProcessorDefinition[_] = rd /** - * Sets the route definition handler for creating a custom route to this consumer instance. - *

- * Java API. + * For internal use only. Converts this ConsumerConfig to camel URI parameters + * @return */ - def onRouteDefinition(h: RouteDefinitionHandler): Unit = routeDefinitionHandler = h -} - -/** - * Java-friendly Consumer. - * - * Subclass this abstract class to create an MDB-style untyped consumer actor. This - * class is meant to be used from Java. - * - * @author Martin Krasser - */ -abstract class UntypedConsumerActor extends UntypedActor with Consumer { - final override def endpointUri = getEndpointUri - final override def blocking = isBlocking - final override def autoack = isAutoack - - /** - * Returns the Camel endpoint URI to consume messages from. - */ - def getEndpointUri(): String - - /** - * Determines whether two-way communications between an endpoint and this consumer actor - * should be done in blocking or non-blocking mode (default is non-blocking). This method - * doesn't have any effect on one-way communications (they'll never block). - */ - def isBlocking() = super.blocking - - /** - * Determines whether one-way communications between an endpoint and this consumer actor - * should be auto-acknowledged or system-acknowledged. - */ - def isAutoack() = super.autoack -} - -/** - * A callback handler for route definitions to consumer actors. - * - * @author Martin Krasser - */ -trait RouteDefinitionHandler { - def onRouteDefinition(rd: RouteDefinition): ProcessorDefinition[_] -} - -/** - * The identity route definition handler. - * - * @author Martin Krasser - * - */ -class RouteDefinitionIdentity extends RouteDefinitionHandler { - def onRouteDefinition(rd: RouteDefinition) = rd -} - -/** - * @author Martin Krasser - */ -object RouteDefinitionHandler { - /** - * Returns the identity route definition handler - */ - val identity = new RouteDefinitionIdentity - - /** - * Created a route definition handler from the given function. - */ - def from(f: RouteDefinition ⇒ ProcessorDefinition[_]) = new RouteDefinitionHandler { - def onRouteDefinition(rd: RouteDefinition) = f(rd) - } -} - -/** - * @author Martin Krasser - */ -private[camel] object Consumer { - /** - * Applies a function f to actorRef if actorRef - * references a consumer actor. A valid reference to a consumer actor is a local actor - * reference with a target actor that implements the Consumer trait. The - * target Consumer instance is passed as argument to f. This - * method returns None if actorRef is not a valid reference - * to a consumer actor, Some contained the return value of f - * otherwise. - */ - def withConsumer[T](actorRef: ActorRef)(f: Consumer ⇒ T): Option[T] = actorRef match { - case l: LocalActorRef ⇒ - l.underlyingActorInstance match { - case c: Consumer ⇒ Some(f(c)) - case _ ⇒ None - } - case _ ⇒ None - } + private[camel] def toCamelParameters: String = "autoack=%s&replyTimeout=%s" format (autoack, DurationTypeConverter.toString(replyTimeout)) } diff --git a/akka-camel/src/main/scala/akka/camel/ConsumerPublisher.scala b/akka-camel/src/main/scala/akka/camel/ConsumerPublisher.scala deleted file mode 100644 index eef98f4f25..0000000000 --- a/akka-camel/src/main/scala/akka/camel/ConsumerPublisher.scala +++ /dev/null @@ -1,221 +0,0 @@ -/** - * Copyright (C) 2009-2010 Typesafe Inc. - */ -package akka.camel - -import java.io.InputStream -import java.util.concurrent.CountDownLatch - -import org.apache.camel.builder.RouteBuilder -import org.apache.camel.model.RouteDefinition - -import akka.actor._ -import akka.event.EventHandler - -/** - * Concrete publish requestor that requests publication of consumer actors on ActorRegistered - * events and unpublication of consumer actors on ActorUnregistered events. - * - * @author Martin Krasser - */ -private[camel] class ConsumerPublishRequestor extends PublishRequestor { - def receiveActorRegistryEvent = { - case ActorRegistered(_, actor) ⇒ for (event ← ConsumerActorRegistered.eventFor(actor)) deliverCurrentEvent(event) - case ActorUnregistered(_, actor) ⇒ for (event ← ConsumerActorUnregistered.eventFor(actor)) deliverCurrentEvent(event) - case _ ⇒ () - } -} - -/** - * Publishes consumer actors on ConsumerActorRegistered events and unpublishes - * consumer actors on ConsumerActorUnregistered events. Publications are tracked - * by sending an activationTracker an EndpointActivated event, - * unpublications are tracked by sending an EndpointActivated event. - * - * @author Martin Krasser - */ -private[camel] class ConsumerPublisher(activationTracker: ActorRef) extends Actor { - import ConsumerPublisher._ - - def receive = { - case r: ConsumerActorRegistered ⇒ { - handleConsumerActorRegistered(r) - activationTracker ! EndpointActivated - } - case u: ConsumerActorUnregistered ⇒ { - handleConsumerActorUnregistered(u) - activationTracker ! EndpointDeactivated - } - case _ ⇒ { /* ignore */ } - } -} - -/** - * @author Martin Krasser - */ -private[camel] object ConsumerPublisher { - /** - * Creates a route to the registered consumer actor. - */ - def handleConsumerActorRegistered(event: ConsumerActorRegistered) { - CamelContextManager.mandatoryContext.addRoutes(new ConsumerActorRouteBuilder(event)) - EventHandler notifyListeners EventHandler.Info(this, "published actor %s at endpoint %s" format (event.actorRef, event.endpointUri)) - } - - /** - * Stops the route to the already un-registered consumer actor. - */ - def handleConsumerActorUnregistered(event: ConsumerActorUnregistered) { - CamelContextManager.mandatoryContext.stopRoute(event.uuid) - EventHandler notifyListeners EventHandler.Info(this, "unpublished actor %s from endpoint %s" format (event.actorRef, event.endpointUri)) - } -} - -/** - * Abstract builder of a route to a target which can be either an actor or an typed actor method. - * - * @param endpointUri endpoint URI of the consumer actor or typed actor method. - * @param id unique route identifier. - * - * @author Martin Krasser - */ -private[camel] abstract class ConsumerRouteBuilder(endpointUri: String, id: String) extends RouteBuilder { - // TODO: make conversions configurable - private val bodyConversions = Map( - "file" -> classOf[InputStream]) - - def configure = { - val schema = endpointUri take endpointUri.indexOf(":") // e.g. "http" from "http://whatever/..." - val cnvopt = bodyConversions.get(schema) - - onRouteDefinition(startRouteDefinition(cnvopt)).to(targetUri) - } - - protected def routeDefinitionHandler: RouteDefinitionHandler - protected def targetUri: String - - private def onRouteDefinition(rd: RouteDefinition) = routeDefinitionHandler.onRouteDefinition(rd) - private def startRouteDefinition(bodyConversion: Option[Class[_]]): RouteDefinition = bodyConversion match { - case Some(clazz) ⇒ from(endpointUri).routeId(id).convertBodyTo(clazz) - case None ⇒ from(endpointUri).routeId(id) - } -} - -/** - * Builder of a route to a consumer actor. - * - * @author Martin Krasser - */ -private[camel] class ConsumerActorRouteBuilder(event: ConsumerActorRegistered) extends ConsumerRouteBuilder(event.endpointUri, event.uuid) { - protected def routeDefinitionHandler: RouteDefinitionHandler = event.routeDefinitionHandler - protected def targetUri = "actor:uuid:%s?blocking=%s&autoack=%s" format (event.uuid, event.blocking, event.autoack) -} - -/** - * Tracks EndpointActivated and EndpointDectivated events. Used to wait for a - * certain number of endpoints activations and de-activations to occur. - * - * @see SetExpectedActivationCount - * @see SetExpectedDeactivationCount - * - * @author Martin Krasser - */ -private[camel] class ActivationTracker extends Actor { - private var activationLatch = new CountDownLatch(0) - private var deactivationLatch = new CountDownLatch(0) - - def receive = { - case SetExpectedActivationCount(num) ⇒ { - activationLatch = new CountDownLatch(num) - sender ! activationLatch - } - case SetExpectedDeactivationCount(num) ⇒ { - deactivationLatch = new CountDownLatch(num) - sender ! deactivationLatch - } - case EndpointActivated ⇒ activationLatch.countDown - case EndpointDeactivated ⇒ deactivationLatch.countDown - } -} - -/** - * Command message that sets the number of expected endpoint activations on ActivationTracker. - */ -private[camel] case class SetExpectedActivationCount(num: Int) - -/** - * Command message that sets the number of expected endpoint de-activations on ActivationTracker. - */ -private[camel] case class SetExpectedDeactivationCount(num: Int) - -/** - * Event message indicating that a single endpoint has been activated. - */ -private[camel] case class EndpointActivated() - -/** - * Event message indicating that a single endpoint has been de-activated. - */ -private[camel] case class EndpointDeactivated() - -/** - * A consumer (un)registration event. - */ -private[camel] trait ConsumerEvent { - val uuid: String -} - -/** - * A consumer actor (un)registration event. - */ -private[camel] trait ConsumerActorEvent extends ConsumerEvent { - val actorRef: ActorRef - val actor: Consumer - - val uuid = actorRef.uuid.toString - val endpointUri = actor.endpointUri - val blocking = actor.blocking - val autoack = actor.autoack - val routeDefinitionHandler = actor.routeDefinitionHandler -} - -/** - * Event indicating that a consumer actor has been registered at the actor registry. - */ -private[camel] case class ConsumerActorRegistered(actorRef: ActorRef, actor: Consumer) extends ConsumerActorEvent - -/** - * Event indicating that a consumer actor has been unregistered from the actor registry. - */ -private[camel] case class ConsumerActorUnregistered(actorRef: ActorRef, actor: Consumer) extends ConsumerActorEvent - -/** - * @author Martin Krasser - */ -private[camel] object ConsumerActorRegistered { - /** - * Creates an ConsumerActorRegistered event message for a consumer actor or None if - * actorRef is not a consumer actor. - */ - def eventFor(actorRef: ActorRef): Option[ConsumerActorRegistered] = { - Consumer.withConsumer[ConsumerActorRegistered](actorRef) { actor ⇒ - ConsumerActorRegistered(actorRef, actor) - } - } -} - -/** - * @author Martin Krasser - */ -private[camel] object ConsumerActorUnregistered { - /** - * Creates an ConsumerActorUnregistered event message for a consumer actor or None if - * actorRef is not a consumer actor. - */ - def eventFor(actorRef: ActorRef): Option[ConsumerActorUnregistered] = { - Consumer.withConsumer[ConsumerActorUnregistered](actorRef) { actor ⇒ - ConsumerActorUnregistered(actorRef, actor) - } - } -} - diff --git a/akka-camel/src/main/scala/akka/camel/Message.scala b/akka-camel/src/main/scala/akka/camel/Message.scala deleted file mode 100644 index e5a553f34e..0000000000 --- a/akka-camel/src/main/scala/akka/camel/Message.scala +++ /dev/null @@ -1,385 +0,0 @@ -/** - * Copyright (C) 2009-2010 Typesafe Inc. - */ -package akka.camel - -import java.util.{ Map ⇒ JMap, Set ⇒ JSet } - -import scala.collection.JavaConversions._ - -import org.apache.camel.{ Exchange, Message ⇒ CamelMessage } -import org.apache.camel.util.ExchangeHelper - -import akka.japi.{ Function ⇒ JFunction } - -/** - * An immutable representation of a Camel message. - * - * @author Martin Krasser - */ -case class Message(val body: Any, val headers: Map[String, Any] = Map.empty) { - - /** - * Creates a Message with given body and empty headers map. - */ - def this(body: Any) = this(body, Map.empty[String, Any]) - - /** - * Creates a Message with given body and headers map. A copy of the headers map is made. - *

- * Java API - */ - def this(body: Any, headers: JMap[String, Any]) = this(body, headers.toMap) - - /** - * Returns the body of the message converted to the type T. Conversion is done - * using Camel's type converter. The type converter is obtained from the CamelContext managed - * by CamelContextManager. Applications have to ensure proper initialization of - * CamelContextManager. - * - * @see CamelContextManager. - */ - def bodyAs[T](implicit m: Manifest[T]): T = getBodyAs(m.erasure.asInstanceOf[Class[T]]) - - /** - * Returns the body of the message converted to the type as given by the clazz - * parameter. Conversion is done using Camel's type converter. The type converter is obtained - * from the CamelContext managed by CamelContextManager. Applications have to ensure proper - * initialization of CamelContextManager. - *

- * Java API - * - * @see CamelContextManager. - */ - def getBodyAs[T](clazz: Class[T]): T = - CamelContextManager.mandatoryContext.getTypeConverter.mandatoryConvertTo[T](clazz, body) - - /** - * Returns those headers from this message whose name is contained in names. - */ - def headers(names: Set[String]): Map[String, Any] = headers.filter(names contains _._1) - - /** - * Returns those headers from this message whose name is contained in names. - * The returned headers map is backed up by an immutable headers map. Any attempt to modify - * the returned map will throw an exception. - *

- * Java API - */ - def getHeaders(names: JSet[String]): JMap[String, Any] = headers.filter(names contains _._1) - - /** - * Returns all headers from this message. The returned headers map is backed up by this - * message's immutable headers map. Any attempt to modify the returned map will throw an - * exception. - *

- * Java API - */ - def getHeaders: JMap[String, Any] = headers - - /** - * Returns the header with given name. Throws NoSuchElementException - * if the header doesn't exist. - */ - def header(name: String): Any = headers(name) - - /** - * Returns the header with given name. Throws NoSuchElementException - * if the header doesn't exist. - *

- * Java API - */ - def getHeader(name: String): Any = header(name) - - /** - * Returns the header with given name converted to type T. Throws - * NoSuchElementException if the header doesn't exist. - */ - def headerAs[T](name: String)(implicit m: Manifest[T]): T = - getHeaderAs(name, m.erasure.asInstanceOf[Class[T]]) - - /** - * Returns the header with given name converted to type as given by the clazz - * parameter. Throws NoSuchElementException if the header doesn't exist. - *

- * Java API - */ - def getHeaderAs[T](name: String, clazz: Class[T]): T = - CamelContextManager.mandatoryContext.getTypeConverter.mandatoryConvertTo[T](clazz, header(name)) - - /** - * Creates a Message with a transformed body using a transformer function. - */ - def transformBody[A](transformer: A ⇒ Any): Message = setBody(transformer(body.asInstanceOf[A])) - - /** - * Creates a Message with a transformed body using a transformer function. - *

- * Java API - */ - def transformBody[A](transformer: JFunction[A, Any]): Message = setBody(transformer(body.asInstanceOf[A])) - - /** - * Creates a Message with current body converted to type T. - */ - def setBodyAs[T](implicit m: Manifest[T]): Message = setBodyAs(m.erasure.asInstanceOf[Class[T]]) - - /** - * Creates a Message with current body converted to type clazz. - *

- * Java API - */ - def setBodyAs[T](clazz: Class[T]): Message = setBody(getBodyAs(clazz)) - - /** - * Creates a Message with a given body. - */ - def setBody(body: Any) = new Message(body, this.headers) - - /** - * Creates a new Message with given headers. - */ - def setHeaders(headers: Map[String, Any]): Message = copy(this.body, headers) - - /** - * Creates a new Message with given headers. A copy of the headers map is made. - *

- * Java API - */ - def setHeaders(headers: JMap[String, Any]): Message = setHeaders(headers.toMap) - - /** - * Creates a new Message with given headers added to the current headers. - */ - def addHeaders(headers: Map[String, Any]): Message = copy(this.body, this.headers ++ headers) - - /** - * Creates a new Message with given headers added to the current headers. - * A copy of the headers map is made. - *

- * Java API - */ - def addHeaders(headers: JMap[String, Any]): Message = addHeaders(headers.toMap) - - /** - * Creates a new Message with the given header added to the current headers. - */ - def addHeader(header: (String, Any)): Message = copy(this.body, this.headers + header) - - /** - * Creates a new Message with the given header, represented by name and - * value added to the existing headers. - *

- * Java API - */ - def addHeader(name: String, value: Any): Message = addHeader((name, value)) - - /** - * Creates a new Message where the header with given headerName is removed from - * the existing headers. - */ - def removeHeader(headerName: String) = copy(this.body, this.headers - headerName) -} - -/** - * Companion object of Message class. - * - * @author Martin Krasser - */ -object Message { - - /** - * Message header to correlate request with response messages. Applications that send - * messages to a Producer actor may want to set this header on the request message - * so that it can be correlated with an asynchronous response. Messages send to Consumer - * actors have this header already set. - */ - val MessageExchangeId = "MessageExchangeId".intern - - /** - * Creates a canonical form of the given message msg. If msg of type - * Message then msg is returned, otherwise msg is set as body of a - * newly created Message object. - */ - def canonicalize(msg: Any) = msg match { - case mobj: Message ⇒ mobj - case body ⇒ new Message(body) - } -} - -/** - * Positive acknowledgement message (used for system-acknowledged message receipts). - * - * @author Martin Krasser - */ -case object Ack { - /** Java API to get the Ack singleton */ - def ack = this -} - -/** - * An immutable representation of a failed Camel exchange. It contains the failure cause - * obtained from Exchange.getException and the headers from either the Exchange.getIn - * message or Exchange.getOut message, depending on the exchange pattern. - * - * @author Martin Krasser - */ -case class Failure(val cause: Throwable, val headers: Map[String, Any] = Map.empty) { - - /** - * Creates a Failure with cause body and empty headers map. - */ - def this(cause: Throwable) = this(cause, Map.empty[String, Any]) - - /** - * Creates a Failure with given cause and headers map. A copy of the headers map is made. - *

- * Java API - */ - def this(cause: Throwable, headers: JMap[String, Any]) = this(cause, headers.toMap) - - /** - * Returns the cause of this Failure. - *

- * Java API. - */ - def getCause = cause - - /** - * Returns all headers from this failure message. The returned headers map is backed up by - * this message's immutable headers map. Any attempt to modify the returned map will throw - * an exception. - *

- * Java API - */ - def getHeaders: JMap[String, Any] = headers -} - -/** - * Adapter for converting an org.apache.camel.Exchange to and from Message and Failure objects. - * - * @author Martin Krasser - */ -class CamelExchangeAdapter(exchange: Exchange) { - import CamelMessageConversion.toMessageAdapter - - /** - * Sets Exchange.getIn from the given Message object. - */ - def fromRequestMessage(msg: Message): Exchange = { requestMessage.fromMessage(msg); exchange } - - /** - * Depending on the exchange pattern, sets Exchange.getIn or Exchange.getOut from the given - * Message object. If the exchange is out-capable then the Exchange.getOut is set, otherwise - * Exchange.getIn. - */ - def fromResponseMessage(msg: Message): Exchange = { responseMessage.fromMessage(msg); exchange } - - /** - * Sets Exchange.getException from the given Failure message. Headers of the Failure message - * are ignored. - */ - def fromFailureMessage(msg: Failure): Exchange = { exchange.setException(msg.cause); exchange } - - /** - * Creates a Message object from Exchange.getIn. - */ - def toRequestMessage: Message = toRequestMessage(Map.empty) - - /** - * Depending on the exchange pattern, creates a Message object from Exchange.getIn or Exchange.getOut. - * If the exchange is out-capable then the Exchange.getOut is set, otherwise Exchange.getIn. - */ - def toResponseMessage: Message = toResponseMessage(Map.empty) - - /** - * Creates a Failure object from the adapted Exchange. - * - * @see Failure - */ - def toFailureMessage: Failure = toFailureMessage(Map.empty) - - /** - * Creates a Message object from Exchange.getIn. - * - * @param headers additional headers to set on the created Message in addition to those - * in the Camel message. - */ - def toRequestMessage(headers: Map[String, Any]): Message = requestMessage.toMessage(headers) - - /** - * Depending on the exchange pattern, creates a Message object from Exchange.getIn or Exchange.getOut. - * If the exchange is out-capable then the Exchange.getOut is set, otherwise Exchange.getIn. - * - * @param headers additional headers to set on the created Message in addition to those - * in the Camel message. - */ - def toResponseMessage(headers: Map[String, Any]): Message = responseMessage.toMessage(headers) - - /** - * Creates a Failure object from the adapted Exchange. - * - * @param headers additional headers to set on the created Message in addition to those - * in the Camel message. - * - * @see Failure - */ - def toFailureMessage(headers: Map[String, Any]): Failure = - Failure(exchange.getException, headers ++ responseMessage.toMessage.headers) - - private def requestMessage = exchange.getIn - - private def responseMessage = ExchangeHelper.getResultMessage(exchange) - -} - -/** - * Adapter for converting an org.apache.camel.Message to and from Message objects. - * - * @author Martin Krasser - */ -class CamelMessageAdapter(val cm: CamelMessage) { - /** - * Set the adapted Camel message from the given Message object. - */ - def fromMessage(m: Message): CamelMessage = { - cm.setBody(m.body) - for (h ← m.headers) cm.getHeaders.put(h._1, h._2.asInstanceOf[AnyRef]) - cm - } - - /** - * Creates a new Message object from the adapted Camel message. - */ - def toMessage: Message = toMessage(Map.empty) - - /** - * Creates a new Message object from the adapted Camel message. - * - * @param headers additional headers to set on the created Message in addition to those - * in the Camel message. - */ - def toMessage(headers: Map[String, Any]): Message = Message(cm.getBody, cmHeaders(headers, cm)) - - private def cmHeaders(headers: Map[String, Any], cm: CamelMessage) = headers ++ cm.getHeaders -} - -/** - * Defines conversion methods to CamelExchangeAdapter and CamelMessageAdapter. - * Imported by applications that implicitly want to use conversion methods of - * CamelExchangeAdapter and CamelMessageAdapter. - */ -object CamelMessageConversion { - - /** - * Creates an CamelExchangeAdapter for the given Camel exchange. - */ - implicit def toExchangeAdapter(ce: Exchange): CamelExchangeAdapter = - new CamelExchangeAdapter(ce) - - /** - * Creates an CamelMessageAdapter for the given Camel message. - */ - implicit def toMessageAdapter(cm: CamelMessage): CamelMessageAdapter = - new CamelMessageAdapter(cm) -} diff --git a/akka-camel/src/main/scala/akka/camel/Producer.scala b/akka-camel/src/main/scala/akka/camel/Producer.scala index 4a9367005a..5a148bb6f9 100644 --- a/akka-camel/src/main/scala/akka/camel/Producer.scala +++ b/akka-camel/src/main/scala/akka/camel/Producer.scala @@ -1,16 +1,12 @@ /** - * Copyright (C) 2009-2010 Typesafe Inc. + * Copyright (C) 2009-2012 Typesafe Inc. */ package akka.camel -import CamelMessageConversion.toExchangeAdapter - -import org.apache.camel._ -import org.apache.camel.processor.SendProcessor - -import akka.actor.{ Actor, ActorRef, UntypedActor } -import akka.dispatch.ActorPromise +import akka.actor.Actor +import internal.CamelExchangeAdapter +import org.apache.camel.{ Exchange, ExchangePattern, AsyncCallback } /** * Support trait for producing messages to Camel endpoints. @@ -18,22 +14,14 @@ import akka.dispatch.ActorPromise * @author Martin Krasser */ trait ProducerSupport { this: Actor ⇒ + protected[this] implicit def camel = CamelExtension(context.system) + + protected[this] lazy val (endpoint, processor) = camel.registerProducer(self, endpointUri) /** - * Message headers to copy by default from request message to response-message. + * CamelMessage headers to copy by default from request message to response-message. */ - private val headersToCopyDefault = Set(Message.MessageExchangeId) - - /** - * Endpoint object resolved from the current CamelContext with - * endpointUri. - */ - private lazy val endpoint = CamelContextManager.mandatoryContext.getEndpoint(endpointUri) - - /** - * SendProcessor for producing messages to endpoint. - */ - private lazy val processor = createSendProcessor + private val headersToCopyDefault = Set(CamelMessage.MessageExchangeId) /** * If set to false (default), this producer expects a response message from the Camel endpoint. @@ -49,33 +37,11 @@ trait ProducerSupport { this: Actor ⇒ /** * Returns the names of message headers to copy from a request message to a response message. - * By default only the Message.MessageExchangeId is copied. Applications may override this to - * define an system-specific set of message headers to copy. + * By default only the CamelMessage.MessageExchangeId is copied. Applications may override this to + * define an application-specific set of message headers to copy. */ def headersToCopy: Set[String] = headersToCopyDefault - /** - * Default implementation of Actor.preRestart for freeing resources needed - * to actually send messages to endpointUri. - */ - override def preRestart(reason: Throwable, msg: Option[Any]) { - try { preRestartProducer(reason) } finally { processor.stop } - } - - /** - * Does nothing by default. Can be overridden by concrete producers for implementing a - * pre-restart callback handler. - */ - def preRestartProducer(reason: Throwable) {} - - /** - * Default implementation of Actor.postStop for freeing resources needed - * to actually send messages to endpointUri. - */ - override def postStop { - processor.stop - } - /** * Initiates a message exchange of given pattern with the endpoint specified by * endpointUri. The in-message of the initiated exchange is the canonical form @@ -83,44 +49,29 @@ trait ProducerSupport { this: Actor ⇒ * as argument to receiveAfterProduce. If the response is received synchronously from * the endpoint then receiveAfterProduce is called synchronously as well. If the * response is received asynchronously, the receiveAfterProduce is called - * asynchronously. This is done by wrapping the response, adding it to this producers - * mailbox, unwrapping it and calling receiveAfterProduce. The original - * sender and senderFuture are thereby preserved. + * asynchronously. The original + * sender and senderFuture are preserved. * - * @see Message#canonicalize(Any) + * @see CamelMessage#canonicalize(Any) * * @param msg message to produce * @param pattern exchange pattern */ - protected def produce(msg: Any, pattern: ExchangePattern) { - val cmsg = Message.canonicalize(msg) - val exchange = createExchange(pattern).fromRequestMessage(cmsg) + protected def produce(msg: Any, pattern: ExchangePattern): Unit = { + implicit def toExchangeAdapter(exchange: Exchange): CamelExchangeAdapter = new CamelExchangeAdapter(exchange) + + val cmsg = CamelMessage.canonicalize(msg) + val exchange = endpoint.createExchange(pattern) + exchange.setRequest(cmsg) processor.process(exchange, new AsyncCallback { val producer = self // Need copies of sender reference here since the callback could be done // later by another thread. - val replyChannel = sender - - def done(doneSync: Boolean) { - (doneSync, exchange.isFailed) match { - case (true, true) ⇒ dispatchSync(exchange.toFailureMessage(cmsg.headers(headersToCopy))) - case (true, false) ⇒ dispatchSync(exchange.toResponseMessage(cmsg.headers(headersToCopy))) - case (false, true) ⇒ dispatchAsync(FailureResult(exchange.toFailureMessage(cmsg.headers(headersToCopy)))) - case (false, false) ⇒ dispatchAsync(MessageResult(exchange.toResponseMessage(cmsg.headers(headersToCopy)))) - } - } - - private def dispatchSync(result: Any) = - receiveAfterProduce(result) - - private def dispatchAsync(result: Any) = { - replyChannel match { - case _: ActorPromise ⇒ - producer.postMessageToMailboxAndCreateFutureResultWithTimeout(result, producer.timeout, replyChannel) - case _ ⇒ - producer.postMessageToMailbox(result, replyChannel) - } - } + val originalSender = sender + // Ignoring doneSync, sending back async uniformly. + def done(doneSync: Boolean): Unit = producer.tell( + if (exchange.isFailed) FailureResult(exchange.toFailureMessage(cmsg.headers(headersToCopy))) + else MessageResult(exchange.toResponseMessage(cmsg.headers(headersToCopy))), originalSender) }) } @@ -162,20 +113,6 @@ trait ProducerSupport { this: Actor ⇒ case msg ⇒ if (!oneway) sender ! msg } - /** - * Creates a new Exchange of given pattern from the endpoint specified by - * endpointUri. - */ - private def createExchange(pattern: ExchangePattern): Exchange = endpoint.createExchange(pattern) - - /** - * Creates a new SendProcessor for endpoint. - */ - private def createSendProcessor = { - val sendProcessor = new SendProcessor(endpoint) - sendProcessor.start() - sendProcessor - } } /** @@ -191,66 +128,14 @@ trait Producer extends ProducerSupport { this: Actor ⇒ } /** - * Subclass this abstract class to create an untyped producer actor. This class is meant to be used from Java. - * * @author Martin Krasser */ -abstract class UntypedProducerActor extends UntypedActor with ProducerSupport { - final override def endpointUri = getEndpointUri - final override def oneway = isOneway - - final override def receiveBeforeProduce = { - case msg ⇒ onReceiveBeforeProduce(msg) - } - - final override def receiveAfterProduce = { - case msg ⇒ onReceiveAfterProduce(msg) - } - - /** - * Default implementation of UntypedActor.onReceive - */ - def onReceive(message: Any) = produce(message) - - /** - * Returns the Camel endpoint URI to produce messages to. - */ - def getEndpointUri(): String - - /** - * If set to false (default), this producer expects a response message from the Camel endpoint. - * If set to true, this producer communicates with the Camel endpoint with an in-only message - * exchange pattern (fire and forget). - */ - def isOneway() = super.oneway - - /** - * Called before the message is sent to the endpoint specified by getEndpointUri. The original - * message is passed as argument. By default, this method simply returns the argument but may be overridden - * by subclasses. - */ - @throws(classOf[Exception]) - def onReceiveBeforeProduce(message: Any): Any = super.receiveBeforeProduce(message) - - /** - * Called after a response was received from the endpoint specified by endpointUri. The - * response is passed as argument. By default, this method sends the response back to the original sender - * if oneway is false. If oneway is true, nothing is - * done. This method may be overridden by subclasses (e.g. to forward responses to another actor). - */ - @throws(classOf[Exception]) - def onReceiveAfterProduce(message: Any): Unit = super.receiveAfterProduce(message) -} +private case class MessageResult(message: CamelMessage) /** * @author Martin Krasser */ -private[camel] case class MessageResult(message: Message) - -/** - * @author Martin Krasser - */ -private[camel] case class FailureResult(failure: Failure) +private case class FailureResult(failure: Failure) /** * A one-way producer. diff --git a/akka-camel/src/main/scala/akka/camel/PublisherRequestor.scala b/akka-camel/src/main/scala/akka/camel/PublisherRequestor.scala deleted file mode 100644 index 7083cdbe6e..0000000000 --- a/akka-camel/src/main/scala/akka/camel/PublisherRequestor.scala +++ /dev/null @@ -1,64 +0,0 @@ -package akka.camel - -import akka.actor._ - -/** - * Base class for concrete (un)publish requestors. Subclasses are responsible for requesting - * (un)publication of consumer actors by calling deliverCurrentEvent. - * - * @author Martin Krasser - */ -private[camel] abstract class PublishRequestor extends Actor { - private val events = collection.mutable.Set[ConsumerEvent]() - private var publisher: Option[ActorRef] = None - - def receiveActorRegistryEvent: Receive - - /** - * Accepts - *

    - *
  • InitPublishRequestor messages to configure a publisher for this requestor.
  • - *
  • ActorRegistryEvent messages to be handled receiveActorRegistryEvent - * implementators
  • . - *
- * Other messages are simply ignored. Calls to deliverCurrentEvent prior to setting a - * publisher are buffered. They will be sent after a publisher has been set. - */ - def receive = { - case InitPublishRequestor(pub) ⇒ { - publisher = Some(pub) - deliverBufferedEvents - } - case e: ActorRegistryEvent ⇒ receiveActorRegistryEvent(e) - case _ ⇒ { /* ignore */ } - } - - /** - * Deliver the given event to publisher or buffer the event if - * publisher is not defined yet. - */ - protected def deliverCurrentEvent(event: ConsumerEvent) { - publisher match { - case Some(pub) ⇒ pub ! event - case None ⇒ events += event - } - } - - private def deliverBufferedEvents { - for (event ← events) deliverCurrentEvent(event) - events.clear - } -} - -/** - * @author Martin Krasser - */ -private[camel] object PublishRequestor { - def pastActorRegisteredEvents = for (actor ← Actor.registry.local.actors) yield ActorRegistered(actor.address, actor) -} - -/** - * Command message to initialize a PublishRequestor to use publisher - * for publishing consumer actors. - */ -private[camel] case class InitPublishRequestor(publisher: ActorRef) diff --git a/akka-camel/src/main/scala/akka/camel/TypedCamelAccess.scala b/akka-camel/src/main/scala/akka/camel/TypedCamelAccess.scala deleted file mode 100644 index 876b65f66a..0000000000 --- a/akka-camel/src/main/scala/akka/camel/TypedCamelAccess.scala +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright (C) 2009-2012 Typesafe Inc. - */ - -package akka.camel - -import org.apache.camel.CamelContext -import akka.event.EventHandler - -import akka.util.ReflectiveAccess.getObjectFor - -/** - * Module for reflective access to akka-camel-typed. - * - * @author Martin Krasser - */ -private[camel] object TypedCamelAccess { - val loader = getClass.getClassLoader - - object TypedCamelModule { - - type TypedCamelObject = { - def onCamelContextInit(context: CamelContext): Unit - def onCamelServiceStart(service: CamelService): Unit - def onCamelServiceStop(service: CamelService): Unit - } - - val typedCamelObject: Option[TypedCamelObject] = - getObjectFor("akka.camel.TypedCamel$", loader) match { - case Right(value) ⇒ Some(value) - case Left(exception) ⇒ - EventHandler.debug(this, exception.toString) - None - } - } -} diff --git a/akka-camel/src/main/scala/akka/camel/component/ActorComponent.scala b/akka-camel/src/main/scala/akka/camel/component/ActorComponent.scala deleted file mode 100644 index c0d0281ab3..0000000000 --- a/akka-camel/src/main/scala/akka/camel/component/ActorComponent.scala +++ /dev/null @@ -1,302 +0,0 @@ -/** - * Copyright (C) 2009-2010 Typesafe Inc. - */ - -package akka.camel.component - -import java.net.InetSocketAddress -import java.util.{ Map ⇒ JMap } -import java.util.concurrent.TimeoutException -import java.util.concurrent.atomic.AtomicReference - -import org.apache.camel._ -import org.apache.camel.impl.{ DefaultProducer, DefaultEndpoint, DefaultComponent } - -import akka.actor._ -import akka.camel.{ Ack, Failure, Message } -import akka.camel.CamelMessageConversion.toExchangeAdapter -import scala.reflect.BeanProperty -import akka.dispatch._ - -/** - * @author Martin Krasser - */ -object ActorComponent { - /** - * Name of the message header containing the actor id or uuid. - */ - val ActorIdentifier = "CamelActorIdentifier" -} - -/** - * Camel component for sending messages to and receiving replies from (untyped) actors. - * - * @see akka.camel.component.ActorEndpoint - * @see akka.camel.component.ActorProducer - * - * @author Martin Krasser - */ -class ActorComponent extends DefaultComponent { - def createEndpoint(uri: String, remaining: String, parameters: JMap[String, Object]): ActorEndpoint = { - val (idType, idValue) = parsePath(remaining) - new ActorEndpoint(uri, this, idType, idValue) - } - - private def parsePath(remaining: String): Tuple2[String, Option[String]] = remaining match { - case null | "" ⇒ throw new IllegalArgumentException("invalid path: [%s] - should be or id: or uuid:" format remaining) - case id if id startsWith "id:" ⇒ ("id", parseIdentifier(id substring 3)) - case uuid if uuid startsWith "uuid:" ⇒ ("uuid", parseIdentifier(uuid substring 5)) - case id ⇒ ("id", parseIdentifier(id)) - } - - private def parseIdentifier(identifier: String): Option[String] = - if (identifier.length > 0) Some(identifier) else None -} - -/** - * Camel endpoint for sending messages to and receiving replies from (untyped) actors. Actors - * are referenced using actor endpoint URIs of the following format: - * actor:, - * actor:id:[] and - * actor:uuid:[], - * where refers to ActorRef.id and - * refers to the String-representation od ActorRef.uuid. In URIs that contain - * id: or uuid:, 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 CamelActorIdentifier and a value that is the target actor's identifier. - * If the URI contains an actor identifier, a message with a CamelActorIdentifier - * header overrides the identifier in the endpoint URI. - * - * @see akka.camel.component.ActorComponent - * @see akka.camel.component.ActorProducer - * - * @author Martin Krasser - */ -class ActorEndpoint(uri: String, - comp: ActorComponent, - val idType: String, - val idValue: Option[String]) extends DefaultEndpoint(uri, comp) { - - /** - * Whether to block caller thread during two-way message exchanges with (untyped) actors. This is - * set via the blocking=true|false endpoint URI parameter. Default value is - * false. - */ - @BeanProperty - var blocking: Boolean = false - - /** - * Whether to auto-acknowledge one-way message exchanges with (untyped) actors. This is - * set via the blocking=true|false endpoint URI parameter. Default value is - * true. When set to true consumer actors need to additionally - * call Consumer.ack within Actor.receive. - */ - @BeanProperty - var autoack: Boolean = true - - /** - * @throws UnsupportedOperationException - */ - def createConsumer(processor: Processor): Consumer = - throw new UnsupportedOperationException("actor consumer not supported yet") - - /** - * Creates a new ActorProducer instance initialized with this endpoint. - */ - def createProducer: ActorProducer = new ActorProducer(this) - - /** - * Returns true. - */ - def isSingleton: Boolean = true -} - -/** - * Sends the in-message of an exchange to an (untyped) actor, identified by an - * actor endpoint URI or by a CamelActorIdentifier message header. - *
    - *
  • If the exchange pattern is out-capable and blocking is set to - * true then the producer waits for a reply, using the !! operator.
  • - *
  • If the exchange pattern is out-capable and blocking is set to - * false then the producer sends the message using the ! operator, together - * with a callback handler. The callback handler is an ActorRef that can be - * used by the receiving actor to asynchronously reply to the route that is sending the - * message.
  • - *
  • If the exchange pattern is in-only then the producer sends the message using the - * ! operator.
  • - *
- * - * @see akka.camel.component.ActorComponent - * @see akka.camel.component.ActorEndpoint - * - * @author Martin Krasser - */ -class ActorProducer(val ep: ActorEndpoint) extends DefaultProducer(ep) with AsyncProcessor { - import ActorProducer._ - - private lazy val uuid = uuidFrom(ep.idValue.getOrElse(throw new ActorIdentifierNotSetException)) - - def process(exchange: Exchange) = - if (exchange.getPattern.isOutCapable) sendSync(exchange) else sendAsync(exchange) - - def process(exchange: Exchange, callback: AsyncCallback): Boolean = { - (exchange.getPattern.isOutCapable, ep.blocking, ep.autoack) match { - case (true, true, _) ⇒ { - sendSync(exchange) - callback.done(true) - true - } - case (true, false, _) ⇒ { - sendAsync(exchange, Some(AsyncCallbackAdapter(exchange, callback))) - false - } - case (false, false, true) ⇒ { - sendAsync(exchange) - callback.done(true) - true - } - case (false, false, false) ⇒ { - sendAsync(exchange, Some(AsyncCallbackAdapter(exchange, callback))) - false - } - case (false, true, false) ⇒ { - sendSync(exchange) - callback.done(true) - true - } - case (false, true, true) ⇒ { - throw new IllegalStateException("cannot have blocking=true and autoack=true for in-only message exchanges") - } - } - } - - private def sendSync(exchange: Exchange) = { - val actor = target(exchange) - val result: Any = try { Some(Await.result((actor ? requestFor(exchange), 5 seconds)) } catch { case e ⇒ Some(Failure(e)) } - - result match { - case Some(Ack) ⇒ { /* no response message to set */ } - case Some(msg: Failure) ⇒ exchange.fromFailureMessage(msg) - case Some(msg) ⇒ exchange.fromResponseMessage(Message.canonicalize(msg)) - case None ⇒ throw new TimeoutException("timeout (%d ms) while waiting response from %s" - format (actor.timeout, ep.getEndpointUri)) - } - } - - private def sendAsync(exchange: Exchange, sender: Option[ActorRef] = None) = - target(exchange).!(requestFor(exchange))(sender) - - private def target(exchange: Exchange) = - targetOption(exchange) getOrElse (throw new ActorNotRegisteredException(ep.getEndpointUri)) - - private def targetOption(exchange: Exchange): Option[ActorRef] = ep.idType match { - case "id" ⇒ targetById(targetId(exchange)) - case "uuid" ⇒ targetByUuid(targetUuid(exchange)) - } - - private def targetId(exchange: Exchange) = exchange.getIn.getHeader(ActorComponent.ActorIdentifier) match { - case id: String ⇒ id - case null ⇒ ep.idValue.getOrElse(throw new ActorIdentifierNotSetException) - } - - private def targetUuid(exchange: Exchange) = exchange.getIn.getHeader(ActorComponent.ActorIdentifier) match { - case uuid: Uuid ⇒ uuid - case uuid: String ⇒ uuidFrom(uuid) - case null ⇒ uuid - } - - private def targetById(id: String) = Actor.registry.local.actorFor(id) - private def targetByUuid(uuid: Uuid) = Actor.registry.local.actorFor(uuid) -} - -/** - * @author Martin Krasser - */ -private[camel] object ActorProducer { - def requestFor(exchange: Exchange) = - exchange.toRequestMessage(Map(Message.MessageExchangeId -> exchange.getExchangeId)) -} - -/** - * Thrown to indicate that an actor referenced by an endpoint URI cannot be - * found in the Actor.registry. - * - * @author Martin Krasser - */ -class ActorNotRegisteredException(uri: String) extends RuntimeException { - override def getMessage = "'%s' not registered" format uri -} - -/** - * Thrown to indicate that no actor identifier has been set. - * - * @author Martin Krasser - */ -class ActorIdentifierNotSetException extends RuntimeException { - override def getMessage = "actor identifier not set" -} - -/** - * @author Martin Krasser - */ -private[akka] object AsyncCallbackAdapter { - /** - * Creates and starts an AsyncCallbackAdapter. - * - * @param exchange message exchange to write results to. - * @param callback callback object to generate completion notifications. - */ - def apply(exchange: Exchange, callback: AsyncCallback) = - new AsyncCallbackAdapter(exchange, callback).start -} - -/** - * Adapts an ActorRef to a Camel AsyncCallback. Used by receiving actors to reply - * asynchronously to Camel routes with ActorRef.reply. - *

- * Please note that this adapter can only be used locally at the moment which should not - * be a problem is most situations since Camel endpoints are only activated for local actor references, - * never for remote references. - * - * @author Martin Krasser - */ -private[akka] class AsyncCallbackAdapter(exchange: Exchange, callback: AsyncCallback) extends ActorRef with ScalaActorRef { - import akka.camel.Consumer._ - - val address = exchange.getExchangeId - - @volatile - private var running: Boolean = true - - def isTerminated: Boolean = !running - - def suspend(): Unit = () - - def resume(): Unit = () - - def stop() { running = false } - - /** - * Populates the initial exchange with the reply message and uses the - * callback handler to notify Camel about the asynchronous completion of the message - * exchange. - * - * @param message reply message - * @param sender ignored - */ - protected[akka] def postMessageToMailbox(message: Any, sender: ActorRef) = if(running) { - message match { - case Ack ⇒ { /* no response message to set */ } - case msg: Failure ⇒ exchange.fromFailureMessage(msg) - case msg ⇒ exchange.fromResponseMessage(Message.canonicalize(msg)) - } - callback.done(false) - } - - def ?(message: Any)(implicit timeout: Timeout): Future[Any] = - Promise.failed(new UnsupportedOperationException("Ask/? is not supported for %s".format(getClass.getName))) - def restart(reason: Throwable): Unit = unsupported - - private def unsupported = throw new UnsupportedOperationException("Not supported for %s" format classOf[AsyncCallbackAdapter].getName) -} - diff --git a/akka-camel/src/main/scala/akka/camel/internal/ActivationTracker.scala b/akka-camel/src/main/scala/akka/camel/internal/ActivationTracker.scala new file mode 100644 index 0000000000..406cd48cbb --- /dev/null +++ b/akka-camel/src/main/scala/akka/camel/internal/ActivationTracker.scala @@ -0,0 +1,117 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + +package akka.camel.internal + +import akka.actor._ +import collection.mutable.WeakHashMap + +/** + * For internal use only. Tracks activation and de-activation of endpoints. + */ +private[akka] final class ActivationTracker extends Actor with ActorLogging { + val activations = new WeakHashMap[ActorRef, ActivationStateMachine] + + /** + * A state machine that keeps track of the endpoint activation status of an actor. + */ + class ActivationStateMachine { + type State = PartialFunction[ActivationMessage, Unit] + + var receive: State = notActivated() + + /** + * Not activated state + * @return a partial function that handles messages in the 'not activated' state + */ + def notActivated(): State = { + var awaitingActivation = List[ActorRef]() + var awaitingDeActivation = List[ActorRef]() + + { + case AwaitActivation(ref) ⇒ awaitingActivation ::= sender + case AwaitDeActivation(ref) ⇒ awaitingDeActivation ::= sender + case msg @ EndpointActivated(ref) ⇒ + awaitingActivation.foreach(_ ! msg) + receive = activated(awaitingDeActivation) + case EndpointFailedToActivate(ref, cause) ⇒ + awaitingActivation.foreach(_ ! EndpointFailedToActivate(ref, cause)) + receive = failedToActivate(cause) + } + } + + /** + * Activated state. + * @param currentAwaitingDeActivation the current ActorRefs awaiting de-activation + * @return a partial function that handles messages in the 'activated' state + */ + def activated(currentAwaitingDeActivation: List[ActorRef]): State = { + var awaitingDeActivation = currentAwaitingDeActivation + + { + case AwaitActivation(ref) ⇒ sender ! EndpointActivated(ref) + case AwaitDeActivation(ref) ⇒ awaitingDeActivation ::= sender + case msg @ EndpointDeActivated(ref) ⇒ + awaitingDeActivation foreach (_ ! msg) + receive = deactivated + case msg @ EndpointFailedToDeActivate(ref, cause) ⇒ + awaitingDeActivation foreach (_ ! msg) + receive = failedToDeActivate(cause) + } + } + + /** + * De-activated state + * @return a partial function that handles messages in the 'de-activated' state + */ + def deactivated: State = { + case AwaitActivation(ref) ⇒ sender ! EndpointActivated(ref) + case AwaitDeActivation(ref) ⇒ sender ! EndpointDeActivated(ref) + } + + /** + * Failed to activate state + * @param cause the cause for the failure + * @return a partial function that handles messages in 'failed to activate' state + */ + def failedToActivate(cause: Throwable): State = { + case AwaitActivation(ref) ⇒ sender ! EndpointFailedToActivate(ref, cause) + case AwaitDeActivation(ref) ⇒ sender ! EndpointFailedToActivate(ref, cause) + } + + /** + * Failed to de-activate state + * @param cause the cause for the failure + * @return a partial function that handles messages in 'failed to de-activate' state + */ + def failedToDeActivate(cause: Throwable): State = { + case AwaitActivation(ref) ⇒ sender ! EndpointActivated(ref) + case AwaitDeActivation(ref) ⇒ sender ! EndpointFailedToDeActivate(ref, cause) + } + + } + + /** + * Subscribes self to messages of type ActivationMessage + */ + override def preStart() { + context.system.eventStream.subscribe(self, classOf[ActivationMessage]) + } + + /** + * + * @return + */ + override def receive = { + case msg @ ActivationMessage(ref) ⇒ + val state = activations.getOrElseUpdate(ref, new ActivationStateMachine) + (state.receive orElse logStateWarning(ref))(msg) + } + + private[this] def logStateWarning(actorRef: ActorRef): Receive = { case msg ⇒ log.warning("Message [{}] not expected in current state of actor [{}]", msg, actorRef) } +} + +case class AwaitActivation(ref: ActorRef) extends ActivationMessage(ref) + +case class AwaitDeActivation(ref: ActorRef) extends ActivationMessage(ref) \ No newline at end of file diff --git a/akka-camel/src/main/scala/akka/camel/internal/CamelExchangeAdapter.scala b/akka-camel/src/main/scala/akka/camel/internal/CamelExchangeAdapter.scala new file mode 100644 index 0000000000..de54693950 --- /dev/null +++ b/akka-camel/src/main/scala/akka/camel/internal/CamelExchangeAdapter.scala @@ -0,0 +1,92 @@ +package akka.camel.internal + +import java.util.{ Map ⇒ JMap, Set ⇒ JSet } + +import scala.collection.JavaConversions._ + +import org.apache.camel.util.ExchangeHelper + +import akka.japi.{ Function ⇒ JFunction } +import org.apache.camel.{ Exchange, Message ⇒ JCamelMessage } +import akka.camel.{ Failure, CamelMessage } + +/** + * For internal use only. + * Adapter for converting an org.apache.camel.Exchange to and from CamelMessage and Failure objects. + * + * @author Martin Krasser + */ +private[camel] class CamelExchangeAdapter(exchange: Exchange) { + def getExchangeId = exchange.getExchangeId + + def isOutCapable = exchange.getPattern.isOutCapable + + /** + * Sets Exchange.getIn from the given CamelMessage object. + */ + def setRequest(msg: CamelMessage) { msg.copyContentTo(request) } + + /** + * Depending on the exchange pattern, sets Exchange.getIn or Exchange.getOut from the given + * CamelMessage object. If the exchange is out-capable then the Exchange.getOut is set, otherwise + * Exchange.getIn. + */ + def setResponse(msg: CamelMessage) { msg.copyContentTo(response) } + + /** + * Sets Exchange.getException from the given Failure message. Headers of the Failure message + * are ignored. + */ + def setFailure(msg: Failure) { exchange.setException(msg.cause) } + + /** + * Creates a CamelMessage object from Exchange.getIn. + */ + def toRequestMessage: CamelMessage = toRequestMessage(Map.empty) + + /** + * Depending on the exchange pattern, creates a CamelMessage object from Exchange.getIn or Exchange.getOut. + * If the exchange is out-capable then the Exchange.getOut is set, otherwise Exchange.getIn. + */ + def toResponseMessage: CamelMessage = toResponseMessage(Map.empty) + + /** + * Creates a Failure object from the adapted Exchange. + * + * @see Failure + */ + def toFailureMessage: Failure = toFailureMessage(Map.empty) + + /** + * Creates a CamelMessage object from Exchange.getIn. + * + * @param headers additional headers to set on the created CamelMessage in addition to those + * in the Camel message. + */ + def toRequestMessage(headers: Map[String, Any]): CamelMessage = CamelMessage.from(request, headers) + + /** + * Depending on the exchange pattern, creates a CamelMessage object from Exchange.getIn or Exchange.getOut. + * If the exchange is out-capable then the Exchange.getOut is set, otherwise Exchange.getIn. + * + * @param headers additional headers to set on the created CamelMessage in addition to those + * in the Camel message. + */ + def toResponseMessage(headers: Map[String, Any]): CamelMessage = CamelMessage.from(response, headers) + + /** + * Creates a Failure object from the adapted Exchange. + * + * @param headers additional headers to set on the created CamelMessage in addition to those + * in the Camel message. + * + * @see Failure + */ + def toFailureMessage(headers: Map[String, Any]): Failure = + Failure(exchange.getException, headers ++ response.getHeaders) + + private def request = exchange.getIn + + private def response: JCamelMessage = ExchangeHelper.getResultMessage(exchange) + +} \ No newline at end of file diff --git a/akka-camel/src/main/scala/akka/camel/internal/ConsumerRegistry.scala b/akka-camel/src/main/scala/akka/camel/internal/ConsumerRegistry.scala new file mode 100644 index 0000000000..bb72e978ae --- /dev/null +++ b/akka-camel/src/main/scala/akka/camel/internal/ConsumerRegistry.scala @@ -0,0 +1,190 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + +package akka.camel.internal + +import akka.camel._ +import component.ActorEndpointPath +import java.io.InputStream + +import org.apache.camel.builder.RouteBuilder + +import akka.actor._ +import collection.mutable +import org.apache.camel.model.RouteDefinition +import org.apache.camel.CamelContext +import akka.util.Duration + +/** + * For internal use only. + * Manages consumer registration. Consumers call registerConsumer method to register themselves when they get created. + * ActorEndpoint uses it to lookup an actor by its path. + */ +private[camel] trait ConsumerRegistry { this: Activation ⇒ + def system: ActorSystem + def context: CamelContext + + /** + * For internal use only. + */ + private[this] lazy val idempotentRegistry = system.actorOf(Props(new IdempotentCamelConsumerRegistry(context))) + /** + * For internal use only. + * @param endpointUri the URI to register the consumer on + * @param consumer the consumer + * @param activationTimeout the timeout for activation + * @return the actorRef to the consumer + */ + private[camel] def registerConsumer(endpointUri: String, consumer: Consumer, activationTimeout: Duration) = { + idempotentRegistry ! RegisterConsumer(endpointUri, consumer.self, consumer) + awaitActivation(consumer.self, activationTimeout) + } +} + +/** + * For internal use only. + * Guarantees idempotent registration of camel consumer endpoints. + * + * Once registered the consumer is watched and unregistered upon termination. + * It also publishes events to the eventStream so interested parties could subscribe to them. + * The main consumer of these events is currently the ActivationTracker. + */ +private[camel] class IdempotentCamelConsumerRegistry(camelContext: CamelContext) extends Actor { + + case class UnregisterConsumer(actorRef: ActorRef) + + val activated = new mutable.HashSet[ActorRef] + + val registrator = context.actorOf(Props(new CamelConsumerRegistrator)) + + def receive = { + case msg @ RegisterConsumer(_, consumer, _) ⇒ + if (!isAlreadyActivated(consumer)) { + activated.add(consumer) + registrator ! msg + } + case msg @ EndpointActivated(consumer) ⇒ + context.watch(consumer) + context.system.eventStream.publish(msg) + case msg @ EndpointFailedToActivate(consumer, _) ⇒ + activated.remove(consumer) + context.system.eventStream.publish(msg) + case Terminated(ref) ⇒ + activated.remove(ref) + registrator ! UnregisterConsumer(ref) + case msg @ EndpointFailedToDeActivate(ref, cause) ⇒ context.system.eventStream.publish(msg) + case msg: EndpointDeActivated ⇒ context.system.eventStream.publish(msg) + } + + def isAlreadyActivated(ref: ActorRef): Boolean = activated.contains(ref) + + class CamelConsumerRegistrator extends Actor with ActorLogging { + + def receive = { + case RegisterConsumer(endpointUri, consumer, consumerConfig) ⇒ + camelContext.addRoutes(new ConsumerActorRouteBuilder(endpointUri, consumer, consumerConfig)) + context.sender ! EndpointActivated(consumer) + log.debug("Published actor [{}] at endpoint [{}]", consumerConfig, endpointUri) + case UnregisterConsumer(consumer) ⇒ + camelContext.stopRoute(consumer.path.toString) + context.sender ! EndpointDeActivated(consumer) + log.debug("Unpublished actor [{}] from endpoint [{}]", consumer, consumer.path) + } + + override def preRestart(reason: Throwable, message: Option[Any]) { + //FIXME check logic + super.preStart() + message match { + case Some(RegisterConsumer(_, consumer, _)) ⇒ sender ! EndpointFailedToActivate(consumer, reason) + case Some(UnregisterConsumer(consumer)) ⇒ sender ! EndpointFailedToDeActivate(consumer, reason) + case _ ⇒ + } + } + } +} + +/** + * For internal use only. A message to register a consumer. + * @param endpointUri the endpointUri to register to + * @param actorRef the actorRef to register as a consumer + * @param config the configuration for the consumer + */ +private[camel] case class RegisterConsumer(endpointUri: String, actorRef: ActorRef, config: ConsumerConfig) + +/** + * For internal use only. + * Abstract builder of a route to a target which can be either an actor or an typed actor method. + * + * @param endpointUri endpoint URI of the consumer actor or typed actor method. + * + * @author Martin Krasser + */ +private[camel] class ConsumerActorRouteBuilder(endpointUri: String, consumer: ActorRef, config: ConsumerConfig) extends RouteBuilder { + + //TODO: what if actorpath contains parameters? Should we use url encoding? But this will look ugly... + protected def targetActorUri = ActorEndpointPath(consumer).toCamelPath(config) + + def configure() { + val scheme = endpointUri take endpointUri.indexOf(":") // e.g. "http" from "http://whatever/..." + + val route = from(endpointUri).routeId(consumer.path.toString) + val converted = Conversions(scheme, route) + val userCustomized = applyUserRouteCustomization(converted) + userCustomized.to(targetActorUri) + } + + def applyUserRouteCustomization(rd: RouteDefinition) = config.onRouteDefinition(rd) + + object Conversions { + // TODO: make conversions configurable + private val bodyConversions = Map( + "file" -> classOf[InputStream]) + + def apply(scheme: String, routeDefinition: RouteDefinition): RouteDefinition = bodyConversions.get(scheme) match { + case Some(clazz) ⇒ routeDefinition.convertBodyTo(clazz) + case None ⇒ routeDefinition + } + } + +} + +/** + * Super class of all activation messages. + */ +abstract class ActivationMessage(val actor: ActorRef) + +/** + * For internal use only. companion object of ActivationMessage + * + */ +private[camel] object ActivationMessage { + def unapply(msg: ActivationMessage): Option[ActorRef] = Some(msg.actor) +} + +/** + * For internal use only. + * Event message indicating that a single endpoint has been activated. + */ +sealed case class EndpointActivated(actorRef: ActorRef) extends ActivationMessage(actorRef) + +/** + * For internal use only. + * Event message indicating that a single endpoint failed tp activate + * @param actorRef the endpoint that failed to activate + * @param cause the cause for failure + */ +sealed case class EndpointFailedToActivate(actorRef: ActorRef, cause: Throwable) extends ActivationMessage(actorRef) + +/** + * For internal use only. + * @param actorRef the endpoint that was de-activated + */ +sealed case class EndpointDeActivated(actorRef: ActorRef) extends ActivationMessage(actorRef) + +/** + * For internal use only. + * @param actorRef the endpoint that failed to de-activate + * @param cause the cause for failure + */ +sealed case class EndpointFailedToDeActivate(actorRef: ActorRef, cause: Throwable) extends ActivationMessage(actorRef) diff --git a/akka-camel/src/main/scala/akka/camel/internal/DefaultCamel.scala b/akka-camel/src/main/scala/akka/camel/internal/DefaultCamel.scala new file mode 100644 index 0000000000..1754bb0073 --- /dev/null +++ b/akka-camel/src/main/scala/akka/camel/internal/DefaultCamel.scala @@ -0,0 +1,63 @@ +package akka.camel.internal + +import akka.actor.ActorSystem +import component.{ DurationTypeConverter, ActorComponent } +import org.apache.camel.CamelContext +import org.apache.camel.impl.DefaultCamelContext +import scala.Predef._ +import akka.event.Logging +import akka.camel.Camel +import akka.util.{ NonFatal, Duration } + +/** + * For internal use only. + * Creates an instance of the Camel subsystem. + * + * @param system is used to create internal actors needed by camel instance. + * Camel doesn't maintain the lifecycle of this actor system. The actor system has to be shut down by the user. + * In the typical scenario, when camel is used with akka extension, it is natural that camel reuses the actor system it extends. + * Also by not creating extra internal actor system we are conserving resources. + */ +private[camel] class DefaultCamel(val system: ActorSystem) extends Camel { + /** + * For internal use only. + */ + private[camel] implicit val log = Logging(system, "Camel") + + lazy val context: CamelContext = { + val ctx = new DefaultCamelContext + ctx.setName(system.name); + ctx.setStreamCaching(true) + ctx.addComponent("actor", new ActorComponent(this)) + ctx.getTypeConverterRegistry.addTypeConverter(classOf[Duration], classOf[String], DurationTypeConverter) + ctx + } + + lazy val template = context.createProducerTemplate() + + /** + * Starts camel and underlying camel context and template. + * Only the creator of Camel should start and stop it. + * @see akka.camel.DefaultCamel#stop() + */ + def start = { + context.start() + try template.start() catch { case NonFatal(e) ⇒ context.stop(); throw e } + log.debug("Started CamelContext[{}] for ActorSystem[{}]", context.getName, system.name) + this + } + + /** + * Stops camel and underlying camel context and template. + * Only the creator of Camel should shut it down. + * There is no need to stop Camel instance, which you get from the CamelExtension, as its lifecycle is bound to the actor system. + * + * @see akka.camel.DefaultCamel#start() + */ + def shutdown() { + try context.stop() finally { + try { template.stop() } catch { case NonFatal(e) ⇒ log.debug("Swallowing non-fatal exception [{}] on stopping Camel producer template", e) } + } + log.debug("Stopped CamelContext[{}] for ActorSystem[{}]", context.getName, system.name) + } +} diff --git a/akka-camel/src/main/scala/akka/camel/internal/ProducerRegistry.scala b/akka-camel/src/main/scala/akka/camel/internal/ProducerRegistry.scala new file mode 100644 index 0000000000..ad0650c980 --- /dev/null +++ b/akka-camel/src/main/scala/akka/camel/internal/ProducerRegistry.scala @@ -0,0 +1,84 @@ +package akka.camel.internal + +import java.util.concurrent.ConcurrentHashMap +import org.apache.camel.processor.SendProcessor +import akka.actor.{ Props, ActorRef, Terminated, Actor } +import org.apache.camel.Endpoint +import akka.camel.Camel +import akka.util.NonFatal + +/** + * Watches the end of life of Producers. + * Removes a Producer from the ProducerRegistry when it is Terminated, + * which in turn stops the SendProcessor. + */ +private class ProducerWatcher(registry: ProducerRegistry) extends Actor { + override def receive = { + case RegisterProducer(actorRef) ⇒ context.watch(actorRef) + case Terminated(actorRef) ⇒ registry.unregisterProducer(actorRef) + } +} + +private case class RegisterProducer(actorRef: ActorRef) + +/** + * For internal use only. + * Manages the Camel objects for Producers. + * Every Producer needs an Endpoint and a SendProcessor + * to produce messages over an Exchange. + */ +private[camel] trait ProducerRegistry { + this: Camel ⇒ + private val camelObjects = new ConcurrentHashMap[ActorRef, (Endpoint, SendProcessor)]() + private val watcher = system.actorOf(Props(new ProducerWatcher(this))) + + private def registerWatch(actorRef: ActorRef) { + watcher ! RegisterProducer(actorRef) + } + + /** + * For internal use only. + * Unregisters Endpoint and SendProcessor and stops the SendProcessor + */ + private[camel] def unregisterProducer(actorRef: ActorRef): Unit = { + // Terminated cannot be sent before the actor is created in the processing of system messages. + Option(camelObjects.remove(actorRef)).foreach { + case (_, processor) ⇒ + try { + processor.stop() + system.eventStream.publish(EndpointDeActivated(actorRef)) + } catch { + case NonFatal(e) ⇒ system.eventStream.publish(EndpointFailedToDeActivate(actorRef, e)) + } + } + } + + /** + * For internal use only. + * Creates Endpoint and SendProcessor and associates the actorRef to these. + * @param actorRef the actorRef of the Producer actor. + * @param endpointUri the endpoint Uri of the producer + * @return Endpoint and SendProcessor registered for the actorRef + */ + private[camel] def registerProducer(actorRef: ActorRef, endpointUri: String): (Endpoint, SendProcessor) = { + try { + val endpoint = context.getEndpoint(endpointUri) + val processor = new SendProcessor(endpoint) + + camelObjects.putIfAbsent(actorRef, (endpoint, processor)) match { + case null ⇒ + processor.start() + registerWatch(actorRef) + system.eventStream.publish(EndpointActivated(actorRef)) + (endpoint, processor) + case prev ⇒ prev + } + } catch { + case NonFatal(e) ⇒ { + system.eventStream.publish(EndpointFailedToActivate(actorRef, e)) + // can't return null to the producer actor, so blow up actor in initialization. + throw e + } + } + } +} \ No newline at end of file diff --git a/akka-camel/src/main/scala/akka/camel/internal/component/ActorComponent.scala b/akka-camel/src/main/scala/akka/camel/internal/component/ActorComponent.scala new file mode 100644 index 0000000000..6374dddcac --- /dev/null +++ b/akka-camel/src/main/scala/akka/camel/internal/component/ActorComponent.scala @@ -0,0 +1,261 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + +package akka.camel.internal.component + +import java.util.{ Map ⇒ JMap } + +import org.apache.camel._ +import org.apache.camel.impl.{ DefaultProducer, DefaultEndpoint, DefaultComponent } + +import akka.actor._ +import akka.pattern._ + +import scala.reflect.BeanProperty +import akka.util.duration._ +import java.util.concurrent.{ TimeoutException, CountDownLatch } +import akka.camel.internal.CamelExchangeAdapter +import akka.util.{ NonFatal, Duration, Timeout } +import akka.camel.{ DefaultConsumerConfig, ConsumerConfig, Camel, Ack, Failure ⇒ CamelFailure, CamelMessage } + +/** + * For internal use only. + * Camel component for sending messages to and receiving replies from (untyped) actors. + * + * @see akka.camel.component.ActorEndpoint + * @see akka.camel.component.ActorProducer + * + * @author Martin Krasser + */ +private[camel] class ActorComponent(camel: Camel) extends DefaultComponent { + def createEndpoint(uri: String, remaining: String, parameters: JMap[String, Object]): ActorEndpoint = { + val path = ActorEndpointPath.fromCamelPath(remaining) + new ActorEndpoint(uri, this, path, camel) + } +} + +/** + * For internal use only. + * The ActorEndpoint is a Camel Endpoint that is used to receive messages from Camel through the ActorComponent + * Actors are referenced using actor endpoint URIs of the following format: + * actor://path:[actorPath]?[options]%s, + * where [actorPath] refers to the Actor Path to the Actor. + * + * @see akka.camel.component.ActorComponent + * @see akka.camel.component.ActorProducer + * + * @author Martin Krasser + */ +private[camel] class ActorEndpoint(uri: String, + comp: ActorComponent, + val path: ActorEndpointPath, + camel: Camel) extends DefaultEndpoint(uri, comp) with ActorEndpointConfig { + + /** + * + * The ActorEndpoint right now only supports receiving messages from Camel. + * The createProducer method (not to be confused with a producer actor) is used to send messages into the endpoint. + * The ActorComponent is only there to send to actors registered through an actor endpoint URI. + * You can use an actor as an endpoint to send to in a camel route (as in, a Camel Consumer Actor). so from(someuri) to (actoruri), but not 'the other way around'. + * Supporting createConsumer would mean that messages are consumed from an Actor endpoint in a route, and an Actor is not necessarily a producer of messages + * [[akka.camel.Producer]] Actors can be used for sending messages to some other uri/ component type registered in Camel. + * @throws UnsupportedOperationException this method is not supported + */ + def createConsumer(processor: Processor): org.apache.camel.Consumer = + throw new UnsupportedOperationException("actor consumer not supported yet") + + /** + * Creates a new ActorProducer instance initialized with this endpoint. + */ + def createProducer: ActorProducer = new ActorProducer(this, camel) + + /** + * Returns true. + */ + def isSingleton: Boolean = true +} + +private[camel] trait ActorEndpointConfig { + def path: ActorEndpointPath + + @BeanProperty var replyTimeout: Duration = 1 minute + + /** + * TODO fix it + * Whether to auto-acknowledge one-way message exchanges with (untyped) actors. This is + * set via the blocking=true|false endpoint URI parameter. Default value is + * true. When set to true consumer actors need to additionally + * call Consumer.ack within Actor.receive. + */ + @BeanProperty var autoack: Boolean = true +} + +/** + * Sends the in-message of an exchange to an untyped actor, identified by an [[akka.camel.internal.component.ActorEndPoint]] + * + * @see akka.camel.component.ActorComponent + * @see akka.camel.component.ActorEndpoint + * + * @author Martin Krasser + */ +private[camel] class ActorProducer(val endpoint: ActorEndpoint, camel: Camel) extends DefaultProducer(endpoint) with AsyncProcessor { + /** + * Processes the exchange. + * Calls the asynchronous version of the method and waits for the result (blocking) + * @param exchange the exchange to process + */ + def process(exchange: Exchange) { processExchangeAdapter(new CamelExchangeAdapter(exchange)) } + + /** + * Processes the message exchange. the caller supports having the exchange asynchronously processed. + * If there was a failure processing then the caused Exception would be set on the Exchange. + * + * @param exchange the message exchange + * @param callback the AsyncCallback will be invoked when the processing of the exchange is completed. + * If the exchange is completed synchronously, then the callback is also invoked synchronously. + * The callback should therefore be careful of starting recursive loop. + * @return (doneSync) true to continue execute synchronously, false to continue being executed asynchronously + */ + def process(exchange: Exchange, callback: AsyncCallback): Boolean = { processExchangeAdapter(new CamelExchangeAdapter(exchange), callback) } + + /** + * For internal use only. Processes the [[akka.camel.internal.CamelExchangeAdapter]] + * @param exchange the [[akka.camel.internal.CamelExchangeAdapter]] + */ + private[camel] def processExchangeAdapter(exchange: CamelExchangeAdapter) { + val isDone = new CountDownLatch(1) + processExchangeAdapter(exchange, new AsyncCallback { def done(doneSync: Boolean) { isDone.countDown() } }) + isDone.await() // this should never wait forever as the process(exchange, callback) method guarantees that. + } + + /** + * For internal use only. Processes the [[akka.camel.internal.CamelExchangeAdapter]]. + * This method is blocking when the exchange is inOnly. The method returns true if it executed synchronously/blocking. + * @param exchange the [[akka.camel.internal.CamelExchangeAdapter]] + * @param callback the callback + * @return (doneSync) true to continue execute synchronously, false to continue being executed asynchronously + */ + private[camel] def processExchangeAdapter(exchange: CamelExchangeAdapter, callback: AsyncCallback): Boolean = { + + // these notify methods are just a syntax sugar + def notifyDoneSynchronously[A](a: A = null) = callback.done(true) + def notifyDoneAsynchronously[A](a: A = null) = callback.done(false) + + def message = messageFor(exchange) + + if (exchange.isOutCapable) { //InOut + sendAsync(message, onComplete = forwardResponseTo(exchange) andThen notifyDoneAsynchronously) + } else { // inOnly + if (endpoint.autoack) { //autoAck + fireAndForget(message, exchange) + notifyDoneSynchronously() + true // done sync + } else { //manualAck + sendAsync(message, onComplete = forwardAckTo(exchange) andThen notifyDoneAsynchronously) + } + } + + } + private def forwardResponseTo(exchange: CamelExchangeAdapter): PartialFunction[Either[Throwable, Any], Unit] = { + case Right(failure: CamelFailure) ⇒ exchange.setFailure(failure); + case Right(msg) ⇒ exchange.setResponse(CamelMessage.canonicalize(msg)) + case Left(e: TimeoutException) ⇒ exchange.setFailure(CamelFailure(new TimeoutException("Failed to get response from the actor [%s] within timeout [%s]. Check replyTimeout and blocking settings [%s]" format (endpoint.path, endpoint.replyTimeout, endpoint)))) + case Left(throwable) ⇒ exchange.setFailure(CamelFailure(throwable)) + } + + private def forwardAckTo(exchange: CamelExchangeAdapter): PartialFunction[Either[Throwable, Any], Unit] = { + case Right(Ack) ⇒ { /* no response message to set */ } + case Right(failure: CamelFailure) ⇒ exchange.setFailure(failure) + case Right(msg) ⇒ exchange.setFailure(CamelFailure(new IllegalArgumentException("Expected Ack or Failure message, but got: [%s] from actor [%s]" format (msg, endpoint.path)))) + case Left(e: TimeoutException) ⇒ exchange.setFailure(CamelFailure(new TimeoutException("Failed to get Ack or Failure response from the actor [%s] within timeout [%s]. Check replyTimeout and blocking settings [%s]" format (endpoint.path, endpoint.replyTimeout, endpoint)))) + case Left(throwable) ⇒ exchange.setFailure(CamelFailure(throwable)) + } + + private def sendAsync(message: CamelMessage, onComplete: PartialFunction[Either[Throwable, Any], Unit]): Boolean = { + try { + val actor = actorFor(endpoint.path) + val future = actor.ask(message)(new Timeout(endpoint.replyTimeout)) + future.onComplete(onComplete) + } catch { + case NonFatal(e) ⇒ onComplete(Left(e)) + } + false // Done async + } + + private def fireAndForget(message: CamelMessage, exchange: CamelExchangeAdapter) { + try { + actorFor(endpoint.path) ! message + } catch { + case e ⇒ exchange.setFailure(new CamelFailure(e)) + } + } + + private[this] def actorFor(path: ActorEndpointPath): ActorRef = + path.findActorIn(camel.system) getOrElse (throw new ActorNotRegisteredException(path.actorPath)) + + private[this] def messageFor(exchange: CamelExchangeAdapter) = + exchange.toRequestMessage(Map(CamelMessage.MessageExchangeId -> exchange.getExchangeId)) + +} + +/** + * Thrown to indicate that an actor referenced by an endpoint URI cannot be + * found in the Actor.registry. + * + * @author Martin Krasser + */ +class ActorNotRegisteredException(uri: String) extends RuntimeException { + override def getMessage = "Actor [%s] doesn't exist" format uri +} + +/** + * For internal use only. + */ +private[camel] object DurationTypeConverter extends CamelTypeConverter { + def convertTo[T](`type`: Class[T], value: AnyRef) = { + Duration(value.toString).asInstanceOf[T] + } + + def toString(duration: Duration) = duration.toNanos + " nanos" +} + +/** + * For internal use only. + */ +private[camel] abstract class CamelTypeConverter extends TypeConverter { + def convertTo[T](`type`: Class[T], exchange: Exchange, value: AnyRef) = convertTo(`type`, value) + def mandatoryConvertTo[T](`type`: Class[T], value: AnyRef) = convertTo(`type`, value) + def mandatoryConvertTo[T](`type`: Class[T], exchange: Exchange, value: AnyRef) = convertTo(`type`, value) +} + +/** + * For internal use only. An endpoint to an ActorRef + * @param actorPath the path to the actor + */ +private[camel] case class ActorEndpointPath private (actorPath: String) { + require(actorPath != null) + require(actorPath.length() > 0) + def toCamelPath(config: ConsumerConfig = DefaultConsumerConfig): String = "actor://path:%s?%s" format (actorPath, config.toCamelParameters) + + def findActorIn(system: ActorSystem): Option[ActorRef] = { + val ref = system.actorFor(actorPath) + if (ref.isTerminated) None else Some(ref) + } + +} + +/** + * For internal use only. Companion of ActorEndpointPath + */ +private[camel] object ActorEndpointPath { + def apply(actorRef: ActorRef) = new ActorEndpointPath(actorRef.path.toString) + + /** + * Expects path in a format: path:%s + */ + def fromCamelPath(camelPath: String) = camelPath match { + case id if id startsWith "path:" ⇒ new ActorEndpointPath(id substring 5) + case _ ⇒ throw new IllegalArgumentException("Invalid path: [%s] - should be path:" format camelPath) + } +} diff --git a/akka-camel/src/main/scala/akka/camel/javaapi/UntypedConsumer.scala b/akka-camel/src/main/scala/akka/camel/javaapi/UntypedConsumer.scala new file mode 100644 index 0000000000..9c21333348 --- /dev/null +++ b/akka-camel/src/main/scala/akka/camel/javaapi/UntypedConsumer.scala @@ -0,0 +1,45 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + +package akka.camel.javaapi + +import akka.actor.UntypedActor +import akka.camel._ +import org.apache.camel.{ ProducerTemplate, CamelContext } + +/** + * Java-friendly Consumer. + * + * @see UntypedConsumerActor + * @see RemoteUntypedConsumerActor + * + * @author Martin Krasser + */ +trait UntypedConsumer extends Consumer { self: UntypedActor ⇒ + final def endpointUri = getEndpointUri + + /** + * Returns the Camel endpoint URI to consume messages from. + */ + def getEndpointUri(): String + +} + +/** + * Subclass this abstract class to create an MDB-style untyped consumer actor. This + * class is meant to be used from Java. + */ +abstract class UntypedConsumerActor extends UntypedActor with UntypedConsumer { + /** + * Returns the [[org.apache.camel.CamelContext]] + * @return the CamelContext + */ + protected def getCamelContext: CamelContext = camelContext + + /** + * Returns the [[org.apache.camel.ProducerTemplate]] + * @return the ProducerTemplate + */ + protected def getProducerTemplate: ProducerTemplate = camel.template +} diff --git a/akka-camel/src/main/scala/akka/camel/javaapi/UntypedProducerActor.scala b/akka-camel/src/main/scala/akka/camel/javaapi/UntypedProducerActor.scala new file mode 100644 index 0000000000..b947e43d64 --- /dev/null +++ b/akka-camel/src/main/scala/akka/camel/javaapi/UntypedProducerActor.scala @@ -0,0 +1,72 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + +package akka.camel.javaapi + +import akka.actor.UntypedActor +import akka.camel._ +import org.apache.camel.{ CamelContext, ProducerTemplate } + +/** + * Subclass this abstract class to create an untyped producer actor. This class is meant to be used from Java. + * + * @author Martin Krasser + */ +abstract class UntypedProducerActor extends UntypedActor with ProducerSupport { + /** + * Called before the message is sent to the endpoint specified by getEndpointUri. The original + * message is passed as argument. By default, this method simply returns the argument but may be overridden + * by subclasses. + */ + def onReceiveBeforeProduce(message: AnyRef): AnyRef = message + + /** + * Called after a response was received from the endpoint specified by endpointUri. The + * response is passed as argument. By default, this method sends the response back to the original sender + * if oneway is false. If oneway is true, nothing is + * done. This method may be overridden by subclasses (e.g. to forward responses to another actor). + */ + def onReceiveAfterProduce(message: AnyRef): Unit = super.receiveAfterProduce(message) + + final override def receiveBeforeProduce = { + case msg: AnyRef ⇒ onReceiveBeforeProduce(msg) + } + + final override def receiveAfterProduce = { + case msg: AnyRef ⇒ onReceiveAfterProduce(msg) + } + + final override def endpointUri = getEndpointUri + + final override def oneway = isOneway + + /** + * Default implementation of UntypedActor.onReceive + */ + def onReceive(message: Any) { + produce(message) + } + + /** + * Returns the Camel endpoint URI to produce messages to. + */ + def getEndpointUri(): String + + /** + * If set to false (default), this producer expects a response message from the Camel endpoint. + * If set to true, this producer communicates with the Camel endpoint with an in-only message + * exchange pattern (fire and forget). + */ + def isOneway() = super.oneway + + /** + * Returns the CamelContext. + */ + def getCamelContext(): CamelContext = camel.context + + /** + * Returns the ProducerTemplate. + */ + def getProducerTemplate(): ProducerTemplate = camel.template +} diff --git a/akka-camel/src/main/scala/akka/camelexamples/ExamplesSupport.scala b/akka-camel/src/main/scala/akka/camelexamples/ExamplesSupport.scala new file mode 100644 index 0000000000..6140075823 --- /dev/null +++ b/akka-camel/src/main/scala/akka/camelexamples/ExamplesSupport.scala @@ -0,0 +1,43 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + +package akka.camelexamples + +import akka.camel._ +import akka.util.duration._ +import akka.actor.{ Actor, OneForOneStrategy } +import akka.actor.SupervisorStrategy._ + +object ExamplesSupport { + val retry3xWithin1s = OneForOneStrategy(maxNrOfRetries = 3, withinTimeRange = 1 second) { + case _: Exception ⇒ Restart + } +} + +class SysOutConsumer extends Consumer { + override def activationTimeout = 10 seconds + def endpointUri = "file://data/input/CamelConsumer" + + protected def receive = { + case msg: CamelMessage ⇒ { + printf("Received '%s'\n", msg.bodyAs[String]) + } + } +} + +class TroubleMaker extends Consumer { + def endpointUri = "WRONG URI" + + println("Trying to instantiate conumer with uri: " + endpointUri) + protected def receive = { case _ ⇒ } +} + +class SysOutActor(implicit camel: Camel) extends Actor { + implicit val camelContext = camel.context + protected def receive = { + case msg: CamelMessage ⇒ { + printf("Received '%s'\n", msg.bodyAs[String]) + } + } +} \ No newline at end of file diff --git a/akka-camel/src/main/scala/akka/camelexamples/README.txt b/akka-camel/src/main/scala/akka/camelexamples/README.txt new file mode 100644 index 0000000000..5317b283ba --- /dev/null +++ b/akka-camel/src/main/scala/akka/camelexamples/README.txt @@ -0,0 +1 @@ +This package is outside of akka.camel because we don't want to use private[camel] features in examples. \ No newline at end of file diff --git a/akka-camel/src/main/scala/akka/camelexamples/RichString.scala b/akka-camel/src/main/scala/akka/camelexamples/RichString.scala new file mode 100644 index 0000000000..4342f3f395 --- /dev/null +++ b/akka-camel/src/main/scala/akka/camelexamples/RichString.scala @@ -0,0 +1,24 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + +package akka.camelexamples + +import java.io.FileWriter + +object RichString { + implicit def toRichString(s: String): RichString = new RichString(s) +} + +class RichString(s: String) { + def saveAs(fileName: String) = write(fileName, s) + def >>(fileName: String) = this.saveAs(fileName) + def <<(content: String) = write(s, content) + + private[this] def write(fileName: String, content: String) { + val f = new FileWriter(fileName) + f.write(content) + f.close() + } +} + diff --git a/akka-camel/src/main/scala/akka/camelexamples/_1_SimpleConsumer.scala b/akka-camel/src/main/scala/akka/camelexamples/_1_SimpleConsumer.scala new file mode 100644 index 0000000000..b495d29229 --- /dev/null +++ b/akka-camel/src/main/scala/akka/camelexamples/_1_SimpleConsumer.scala @@ -0,0 +1,21 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + +package akka.camelexamples + +import akka.actor.{ Props, ActorSystem } +import RichString._ + +object _1_SimpleConsumer extends App { + val system = ActorSystem("test") + + system.actorOf(Props[SysOutConsumer]) + + "data/input/CamelConsumer/file1.txt" << "test data " + math.random + + Thread.sleep(2000) + + system.shutdown() + +} \ No newline at end of file diff --git a/akka-camel/src/main/scala/akka/camelexamples/_2_SupervisedConsumers.scala b/akka-camel/src/main/scala/akka/camelexamples/_2_SupervisedConsumers.scala new file mode 100644 index 0000000000..fe7b764e7a --- /dev/null +++ b/akka-camel/src/main/scala/akka/camelexamples/_2_SupervisedConsumers.scala @@ -0,0 +1,39 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + +package akka.camelexamples + +import akka.actor.{ PoisonPill, Terminated, Props, ActorSystem, Actor } +import ExamplesSupport._ +import RichString._ + +object SupervisedConsumersExample extends App { + + val system = ActorSystem("test1") + + system.actorOf(Props(new Actor { + context.watch(context.actorOf(Props[EndpointManager])) + protected def receive = { + case Terminated(ref) ⇒ system.shutdown() + } + })) + + "data/input/CamelConsumer/file1.txt" << "test data " + math.random +} + +class EndpointManager extends Actor { + import context._ + + override def supervisorStrategy() = retry3xWithin1s + + watch(actorOf(Props[SysOutConsumer])) + watch(actorOf(Props[TroubleMaker])) + + protected def receive = { + case Terminated(ref) ⇒ { + printf("Hey! One of the endpoints has died: %s. I am doing sepuku...\n", ref) + self ! PoisonPill + } + } +} diff --git a/akka-camel/src/main/scala/akka/camelexamples/_3_SimpleActorEndpoint.scala b/akka-camel/src/main/scala/akka/camelexamples/_3_SimpleActorEndpoint.scala new file mode 100644 index 0000000000..2e39fef889 --- /dev/null +++ b/akka-camel/src/main/scala/akka/camelexamples/_3_SimpleActorEndpoint.scala @@ -0,0 +1,31 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + +package akka.camelexamples + +import org.apache.camel.builder.RouteBuilder +import akka.actor.{ Props, ActorSystem } +import akka.camel._ +import RichString._ + +object _3_SimpleActorEndpoint extends App { + + val system = ActorSystem("test") + val camel = CamelExtension(system) + + val actor = system.actorOf(Props[SysOutActor]) + + camel.context.addRoutes(new RouteBuilder() { + def configure() { + from("file://data/input/CamelConsumer").to(actor) + } + }) + + "data/input/CamelConsumer/file1.txt" << "test data " + math.random + + Thread.sleep(3000) + + system.shutdown() + +} \ No newline at end of file diff --git a/akka-camel/src/main/scala/akka/package.scala b/akka-camel/src/main/scala/akka/package.scala new file mode 100644 index 0000000000..436d2fc1b3 --- /dev/null +++ b/akka-camel/src/main/scala/akka/package.scala @@ -0,0 +1,11 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + +package akka + +import org.apache.camel.model.ProcessorDefinition + +package object camel { + implicit def toActorRouteDefinition(definition: ProcessorDefinition[_]) = new ActorRouteDefinition(definition) +} \ No newline at end of file diff --git a/akka-camel/src/test/java/akka/camel/ConsumerJavaTestBase.java b/akka-camel/src/test/java/akka/camel/ConsumerJavaTestBase.java index 1fa971eb48..fec093d108 100644 --- a/akka-camel/src/test/java/akka/camel/ConsumerJavaTestBase.java +++ b/akka-camel/src/test/java/akka/camel/ConsumerJavaTestBase.java @@ -1,46 +1,41 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + package akka.camel; -import akka.japi.SideEffect; - +import akka.actor.ActorRef; +import akka.actor.ActorSystem; +import akka.actor.Props; +import akka.util.FiniteDuration; import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Ignore; import org.junit.Test; -import static akka.actor.Actors.*; -import static akka.camel.CamelContextManager.*; -import static akka.camel.CamelServiceManager.*; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.assertEquals; -import static org.junit.Assert.*; /** * @author Martin Krasser */ public class ConsumerJavaTestBase { - @BeforeClass - public static void setUpBeforeClass() { - startCamelService(); - } + static ActorSystem system = ActorSystem.create("test"); + static Camel camel = (Camel) CamelExtension.get(system); + @AfterClass public static void tearDownAfterClass() { - stopCamelService(); - registry().local().shutdownAll(); + system.shutdown(); } - @Test @Ignore // TODO: fix race - - // org.apache.camel.CamelExchangeException: No consumers available - // on endpoint: Endpoint[direct://error-handler-test-java] - + @Test public void shouldHandleExceptionThrownByActorAndGenerateCustomResponse() { - getMandatoryService().awaitEndpointActivation(1, new SideEffect() { - public void apply() { - actorOf(SampleErrorHandlingConsumer.class); - } - }); - String result = getMandatoryTemplate().requestBody("direct:error-handler-test-java", "hello", String.class); + ActorRef ref = system.actorOf(new Props().withCreator(SampleErrorHandlingConsumer.class)); + camel.awaitActivation(ref, new FiniteDuration(1, TimeUnit.SECONDS)); + + String result = camel.template().requestBody("direct:error-handler-test-java", "hello", String.class); assertEquals("error: hello", result); } } diff --git a/akka-camel/src/test/java/akka/camel/MessageJavaTestBase.java b/akka-camel/src/test/java/akka/camel/MessageJavaTestBase.java index 21734c037d..ba483e7a13 100644 --- a/akka-camel/src/test/java/akka/camel/MessageJavaTestBase.java +++ b/akka-camel/src/test/java/akka/camel/MessageJavaTestBase.java @@ -1,109 +1,126 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + package akka.camel; +import akka.actor.ActorSystem; +import akka.japi.Function; import org.apache.camel.NoTypeConversionAvailableException; +import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; -import akka.camel.CamelContextManager; -import akka.camel.Message; -import akka.japi.Function; - import java.io.InputStream; import java.util.*; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; /** * @author Martin Krasser */ public class MessageJavaTestBase { + static Camel camel; + private static ActorSystem system; + private Map empty = new HashMap(); @BeforeClass public static void setUpBeforeClass() { - CamelContextManager.init(); + system = ActorSystem.create("test"); + camel = (Camel) CamelExtension.get(system); } + @AfterClass + public static void cleanup(){ + system.shutdown(); + } + + CamelMessage message(Object body){ return new CamelMessage(body, new HashMap()); } + CamelMessage message(Object body, Map headers){ return new CamelMessage(body, headers); } + + @Test public void shouldConvertDoubleBodyToString() { - assertEquals("1.4", new Message("1.4").getBodyAs(String.class)); + assertEquals("1.4", message("1.4", empty).getBodyAs(String.class,camel.context())); } @Test(expected=NoTypeConversionAvailableException.class) public void shouldThrowExceptionWhenConvertingDoubleBodyToInputStream() { - new Message(1.4).getBodyAs(InputStream.class); + message(1.4).getBodyAs(InputStream.class,camel.context()); } + @Test public void shouldReturnDoubleHeader() { - Message message = new Message("test" , createMap("test", 1.4)); + CamelMessage message = message("test" , createMap("test", 1.4)); assertEquals(1.4, message.getHeader("test")); } @Test public void shouldConvertDoubleHeaderToString() { - Message message = new Message("test" , createMap("test", 1.4)); - assertEquals("1.4", message.getHeaderAs("test", String.class)); + CamelMessage message = message("test" , createMap("test", 1.4)); + assertEquals("1.4", message.getHeaderAs("test", String.class,camel.context())); } @Test public void shouldReturnSubsetOfHeaders() { - Message message = new Message("test" , createMap("A", "1", "B", "2")); + CamelMessage message = message("test" , createMap("A", "1", "B", "2")); assertEquals(createMap("B", "2"), message.getHeaders(createSet("B"))); } @Test(expected=UnsupportedOperationException.class) public void shouldReturnSubsetOfHeadersUnmodifiable() { - Message message = new Message("test" , createMap("A", "1", "B", "2")); + CamelMessage message = message("test" , createMap("A", "1", "B", "2")); message.getHeaders(createSet("B")).put("x", "y"); } @Test public void shouldReturnAllHeaders() { - Message message = new Message("test" , createMap("A", "1", "B", "2")); + CamelMessage message = message("test" , createMap("A", "1", "B", "2")); assertEquals(createMap("A", "1", "B", "2"), message.getHeaders()); } @Test(expected=UnsupportedOperationException.class) public void shouldReturnAllHeadersUnmodifiable() { - Message message = new Message("test" , createMap("A", "1", "B", "2")); + CamelMessage message = message("test" , createMap("A", "1", "B", "2")); message.getHeaders().put("x", "y"); } @Test public void shouldTransformBodyAndPreserveHeaders() { assertEquals( - new Message("ab", createMap("A", "1")), - new Message("a" , createMap("A", "1")).transformBody((Function) new TestTransformer())); + message("ab", createMap("A", "1")), + message("a" , createMap("A", "1")).mapBody(new TestTransformer())); } @Test public void shouldConvertBodyAndPreserveHeaders() { assertEquals( - new Message("1.4", createMap("A", "1")), - new Message(1.4 , createMap("A", "1")).setBodyAs(String.class)); + message("1.4", createMap("A", "1")), + message(1.4 , createMap("A", "1")).withBodyAs(String.class,camel.context())); } @Test public void shouldSetBodyAndPreserveHeaders() { assertEquals( - new Message("test2" , createMap("A", "1")), - new Message("test1" , createMap("A", "1")).setBody("test2")); + message("test2" , createMap("A", "1")), + message("test1" , createMap("A", "1")).withBody("test2")); } @Test public void shouldSetHeadersAndPreserveBody() { assertEquals( - new Message("test1" , createMap("C", "3")), - new Message("test1" , createMap("A", "1")).setHeaders(createMap("C", "3"))); + message("test1" , createMap("C", "3")), + message("test1" , createMap("A", "1")).withHeaders(createMap("C", "3"))); } @Test public void shouldAddHeaderAndPreserveBodyAndHeaders() { assertEquals( - new Message("test1" , createMap("A", "1", "B", "2")), - new Message("test1" , createMap("A", "1")).addHeader("B", "2")); + message("test1" , createMap("A", "1", "B", "2")), + message("test1" , createMap("A", "1")).addHeader("B", "2")); } @Test public void shouldAddHeadersAndPreserveBodyAndHeaders() { assertEquals( - new Message("test1" , createMap("A", "1", "B", "2")), - new Message("test1" , createMap("A", "1")).addHeaders(createMap("B", "2"))); + message("test1" , createMap("A", "1", "B", "2")), + message("test1" , createMap("A", "1")).addHeaders(createMap("B", "2"))); } @Test public void shouldRemoveHeadersAndPreserveBodyAndRemainingHeaders() { assertEquals( - new Message("test1" , createMap("A", "1")), - new Message("test1" , createMap("A", "1", "B", "2")).removeHeader("B")); + message("test1" , createMap("A", "1")), + message("test1" , createMap("A", "1", "B", "2")).withoutHeader("B")); } private static Set createSet(String... entries) { @@ -120,7 +137,7 @@ public class MessageJavaTestBase { return map; } - private static class TestTransformer implements Function { + private static class TestTransformer implements Function { public String apply(String param) { return param + "b"; } diff --git a/akka-camel/src/test/java/akka/camel/SampleErrorHandlingConsumer.java b/akka-camel/src/test/java/akka/camel/SampleErrorHandlingConsumer.java index 4e35d4e6ab..0622a9a51a 100644 --- a/akka-camel/src/test/java/akka/camel/SampleErrorHandlingConsumer.java +++ b/akka-camel/src/test/java/akka/camel/SampleErrorHandlingConsumer.java @@ -1,8 +1,15 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + package akka.camel; +import akka.camel.javaapi.UntypedConsumerActor; +import akka.util.Duration; import org.apache.camel.builder.Builder; import org.apache.camel.model.ProcessorDefinition; import org.apache.camel.model.RouteDefinition; +import scala.Option; /** * @author Martin Krasser @@ -13,22 +20,28 @@ public class SampleErrorHandlingConsumer extends UntypedConsumerActor { return "direct:error-handler-test-java"; } - public boolean isBlocking() { - return true; + @Override + //TODO write test confirming this gets called in java + public ProcessorDefinition onRouteDefinition(RouteDefinition rd) { + return rd.onException(Exception.class).handled(true).transform(Builder.exceptionMessage()).end(); } - public void preStart() { - onRouteDefinition(new RouteDefinitionHandler() { - public ProcessorDefinition onRouteDefinition(RouteDefinition rd) { - return rd.onException(Exception.class).handled(true).transform(Builder.exceptionMessage()).end(); - } - }); + @Override + public Duration replyTimeout(){ + return Duration.create(1, "second"); } + + public void onReceive(Object message) throws Exception { - Message msg = (Message)message; - String body = msg.getBodyAs(String.class); + CamelMessage msg = (CamelMessage) message; + String body = msg.getBodyAs(String.class,this.getCamelContext()); throw new Exception(String.format("error: %s", body)); - } + } + + @Override + public void preRestart(Throwable reason, Option message){ + getSender().tell(new Failure(reason)); + } } diff --git a/akka-camel/src/test/java/akka/camel/SampleUntypedActor.java b/akka-camel/src/test/java/akka/camel/SampleUntypedActor.java index 7559fbe545..e1c17900c3 100644 --- a/akka-camel/src/test/java/akka/camel/SampleUntypedActor.java +++ b/akka-camel/src/test/java/akka/camel/SampleUntypedActor.java @@ -1,3 +1,7 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + package akka.camel; import akka.actor.UntypedActor; diff --git a/akka-camel/src/test/java/akka/camel/SampleUntypedConsumer.java b/akka-camel/src/test/java/akka/camel/SampleUntypedConsumer.java index 0a0c0c7c35..f52a484ccc 100644 --- a/akka-camel/src/test/java/akka/camel/SampleUntypedConsumer.java +++ b/akka-camel/src/test/java/akka/camel/SampleUntypedConsumer.java @@ -1,6 +1,10 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + package akka.camel; -import akka.camel.UntypedConsumerActor; +import akka.camel.javaapi.UntypedConsumerActor; /** * @author Martin Krasser @@ -12,10 +16,10 @@ public class SampleUntypedConsumer extends UntypedConsumerActor { } public void onReceive(Object message) { - Message msg = (Message)message; - String body = msg.getBodyAs(String.class); - String header = msg.getHeaderAs("test", String.class); - sender.tell(String.format("%s %s", body, header)); + CamelMessage msg = (CamelMessage)message; + String body = msg.getBodyAs(String.class, getCamelContext()); + String header = msg.getHeaderAs("test", String.class,getCamelContext()); + sender().tell(String.format("%s %s", body, header)); } } diff --git a/akka-camel/src/test/java/akka/camel/SampleUntypedConsumerBlocking.java b/akka-camel/src/test/java/akka/camel/SampleUntypedConsumerBlocking.java deleted file mode 100644 index 5b29ab2300..0000000000 --- a/akka-camel/src/test/java/akka/camel/SampleUntypedConsumerBlocking.java +++ /dev/null @@ -1,23 +0,0 @@ -package akka.camel; - -/** - * @author Martin Krasser - */ -public class SampleUntypedConsumerBlocking extends UntypedConsumerActor { - - public String getEndpointUri() { - return "direct:test-untyped-consumer-blocking"; - } - - public boolean isBlocking() { - return true; - } - - public void onReceive(Object message) { - Message msg = (Message)message; - String body = msg.getBodyAs(String.class); - String header = msg.getHeaderAs("test", String.class); - sender.tell(String.format("%s %s", body, header)); - } - -} diff --git a/akka-camel/src/test/java/akka/camel/SampleUntypedForwardingProducer.java b/akka-camel/src/test/java/akka/camel/SampleUntypedForwardingProducer.java index 3161c0f2d8..ef0b7465c5 100644 --- a/akka-camel/src/test/java/akka/camel/SampleUntypedForwardingProducer.java +++ b/akka-camel/src/test/java/akka/camel/SampleUntypedForwardingProducer.java @@ -1,5 +1,10 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + package akka.camel; +import akka.camel.javaapi.UntypedProducerActor; /** * @author Martin Krasser */ @@ -11,8 +16,8 @@ public class SampleUntypedForwardingProducer extends UntypedProducerActor { @Override public void onReceiveAfterProduce(Object message) { - Message msg = (Message)message; - String body = msg.getBodyAs(String.class); - CamelContextManager.getMandatoryTemplate().sendBody("direct:forward-test-1", body); + CamelMessage msg = (CamelMessage)message; + String body = msg.getBodyAs(String.class,getCamelContext()); + getProducerTemplate().sendBody("direct:forward-test-1", body); } } diff --git a/akka-camel/src/test/java/akka/camel/SampleUntypedReplyingProducer.java b/akka-camel/src/test/java/akka/camel/SampleUntypedReplyingProducer.java index 09b7b86502..039494fd00 100644 --- a/akka-camel/src/test/java/akka/camel/SampleUntypedReplyingProducer.java +++ b/akka-camel/src/test/java/akka/camel/SampleUntypedReplyingProducer.java @@ -1,5 +1,11 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + package akka.camel; +import akka.camel.javaapi.UntypedProducerActor; + /** * @author Martin Krasser */ diff --git a/akka-camel/src/test/resources/logback.xml b/akka-camel/src/test/resources/logback.xml deleted file mode 100644 index 023f2cd317..0000000000 --- a/akka-camel/src/test/resources/logback.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - [%4p] [%d{ISO8601}] [%t] %c{1}: %m%n - - - - - - diff --git a/akka-camel/src/test/scala/akka/camel/ActivationIntegrationTest.scala b/akka-camel/src/test/scala/akka/camel/ActivationIntegrationTest.scala new file mode 100644 index 0000000000..4fc15e8b5b --- /dev/null +++ b/akka-camel/src/test/scala/akka/camel/ActivationIntegrationTest.scala @@ -0,0 +1,74 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + +package akka.camel + +import org.scalatest.matchers.MustMatchers +import akka.util.duration._ +import org.apache.camel.ProducerTemplate +import akka.actor._ +import akka.util.Timeout +import TestSupport._ +import org.scalatest.WordSpec +import akka.testkit.TestLatch +import akka.dispatch.Await + +class ActivationIntegrationTest extends WordSpec with MustMatchers with SharedCamelSystem { + implicit val timeout = Timeout(10 seconds) + def template: ProducerTemplate = camel.template + + def testActorWithEndpoint(uri: String): ActorRef = { system.actorOf(Props(new TestConsumer(uri))) } + + "ActivationAware must be notified when endpoint is activated" in { + val actor = testActorWithEndpoint("direct:actor-1") + try { + camel.awaitActivation(actor, 1 second) + } catch { + case e: ActivationTimeoutException ⇒ fail("Failed to get notification within 1 second") + } + + template.requestBody("direct:actor-1", "test") must be("received test") + } + + "ActivationAware must be notified when endpoint is de-activated" in { + val latch = TestLatch() + val actor = start(new Consumer { + def endpointUri = "direct:a3" + def receive = { case _ ⇒ {} } + + override def postStop() = { + super.postStop() + latch.countDown() + } + }) + camel.awaitActivation(actor, 1 second) + + system.stop(actor) + camel.awaitDeactivation(actor, 1 second) + Await.ready(latch, 1 second) + } + + "ActivationAware must time out when waiting for endpoint de-activation for too long" in { + val actor = start(new TestConsumer("direct:a5")) + camel.awaitActivation(actor, 1 second) + intercept[DeActivationTimeoutException] { + camel.awaitDeactivation(actor, 1 millis) + } + } + + "awaitActivation must fail if notification timeout is too short and activation is not complete yet" in { + val actor = testActorWithEndpoint("direct:actor-4") + intercept[ActivationTimeoutException] { + camel.awaitActivation(actor, 0 seconds) + } + } + + class TestConsumer(uri: String) extends Consumer { + def endpointUri = uri + override def receive = { + case msg: CamelMessage ⇒ sender ! "received " + msg.body + } + } + +} \ No newline at end of file diff --git a/akka-camel/src/test/scala/akka/camel/CamelContextLifecycleTest.scala b/akka-camel/src/test/scala/akka/camel/CamelContextLifecycleTest.scala deleted file mode 100644 index 58ba676685..0000000000 --- a/akka-camel/src/test/scala/akka/camel/CamelContextLifecycleTest.scala +++ /dev/null @@ -1,37 +0,0 @@ -package akka.camel - -import org.apache.camel.impl.{ DefaultProducerTemplate, DefaultCamelContext } -import org.junit.Test -import org.scalatest.junit.JUnitSuite - -class CamelContextLifecycleTest extends JUnitSuite with CamelContextLifecycle { - @Test - def shouldManageCustomCamelContext { - assert(context === None) - assert(template === None) - - intercept[IllegalStateException] { mandatoryContext } - intercept[IllegalStateException] { mandatoryTemplate } - - val ctx = new TestCamelContext - assert(ctx.isStreamCaching === false) - - init(ctx) - - assert(mandatoryContext.isStreamCaching === true) - assert(!mandatoryContext.asInstanceOf[TestCamelContext].isStarted) - assert(mandatoryTemplate.asInstanceOf[DefaultProducerTemplate].isStarted) - - start - - assert(mandatoryContext.asInstanceOf[TestCamelContext].isStarted) - assert(mandatoryTemplate.asInstanceOf[DefaultProducerTemplate].isStarted) - - stop - - assert(!mandatoryContext.asInstanceOf[TestCamelContext].isStarted) - assert(!mandatoryTemplate.asInstanceOf[DefaultProducerTemplate].isStarted) - } - - class TestCamelContext extends DefaultCamelContext -} diff --git a/akka-camel/src/test/scala/akka/camel/CamelExchangeAdapterTest.scala b/akka-camel/src/test/scala/akka/camel/CamelExchangeAdapterTest.scala index 7668aa2c65..676062a2b7 100644 --- a/akka-camel/src/test/scala/akka/camel/CamelExchangeAdapterTest.scala +++ b/akka-camel/src/test/scala/akka/camel/CamelExchangeAdapterTest.scala @@ -1,95 +1,96 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + package akka.camel -import org.apache.camel.impl.{ DefaultCamelContext, DefaultExchange } -import org.apache.camel.ExchangePattern -import org.junit.Test -import org.scalatest.junit.JUnitSuite +import internal.CamelExchangeAdapter +import org.apache.camel.impl.DefaultExchange +import org.apache.camel.{ Exchange, ExchangePattern } +import akka.camel.TestSupport.{ SharedCamelSystem, MessageSugar } +import org.scalatest.FunSuite -class CamelExchangeAdapterTest extends JUnitSuite { - import CamelMessageConversion.toExchangeAdapter +class CamelExchangeAdapterTest extends FunSuite with SharedCamelSystem with MessageSugar { - @Test - def shouldSetInMessageFromRequestMessage = { - val e1 = sampleInOnly.fromRequestMessage(Message("x")) + //TODO: Get rid of implicit. + // It is here, as was easier to add this implicit than to rewrite the whole test... + implicit def exchangeToAdapter(e: Exchange) = new CamelExchangeAdapter(e) + + test("mustSetInMessageFromRequestMessage") { + val e1 = sampleInOnly + e1.setRequest(Message("x")) assert(e1.getIn.getBody === "x") - val e2 = sampleInOut.fromRequestMessage(Message("y")) + val e2 = sampleInOut + e2.setRequest(Message("y")) assert(e2.getIn.getBody === "y") } - @Test - def shouldSetOutMessageFromResponseMessage = { - val e1 = sampleInOut.fromResponseMessage(Message("y")) + test("mustSetOutMessageFromResponseMessage") { + val e1 = sampleInOut + e1.setResponse(Message("y")) assert(e1.getOut.getBody === "y") } - @Test - def shouldSetInMessageFromResponseMessage = { - val e1 = sampleInOnly.fromResponseMessage(Message("x")) + test("mustSetInMessageFromResponseMessage") { + val e1 = sampleInOnly + e1.setResponse(Message("x")) assert(e1.getIn.getBody === "x") } - @Test - def shouldSetExceptionFromFailureMessage = { - val e1 = sampleInOnly.fromFailureMessage(Failure(new Exception("test1"))) + test("mustSetExceptionFromFailureMessage") { + val e1 = sampleInOnly + e1.setFailure(Failure(new Exception("test1"))) assert(e1.getException.getMessage === "test1") - val e2 = sampleInOut.fromFailureMessage(Failure(new Exception("test2"))) + val e2 = sampleInOut + e2.setFailure(Failure(new Exception("test2"))) assert(e2.getException.getMessage === "test2") } - @Test - def shouldCreateRequestMessageFromInMessage = { + test("mustCreateRequestMessageFromInMessage") { val m = sampleInOnly.toRequestMessage assert(m === Message("test-in", Map("key-in" -> "val-in"))) } - @Test - def shouldCreateResponseMessageFromInMessage = { + test("mustCreateResponseMessageFromInMessage") { val m = sampleInOnly.toResponseMessage assert(m === Message("test-in", Map("key-in" -> "val-in"))) } - @Test - def shouldCreateResponseMessageFromOutMessage = { + test("mustCreateResponseMessageFromOutMessage") { val m = sampleInOut.toResponseMessage assert(m === Message("test-out", Map("key-out" -> "val-out"))) } - @Test - def shouldCreateFailureMessageFromExceptionAndInMessage = { + test("mustCreateFailureMessageFromExceptionAndInMessage") { val e1 = sampleInOnly e1.setException(new Exception("test1")) assert(e1.toFailureMessage.cause.getMessage === "test1") assert(e1.toFailureMessage.headers("key-in") === "val-in") } - @Test - def shouldCreateFailureMessageFromExceptionAndOutMessage = { + test("mustCreateFailureMessageFromExceptionAndOutMessage") { val e1 = sampleInOut e1.setException(new Exception("test2")) assert(e1.toFailureMessage.cause.getMessage === "test2") assert(e1.toFailureMessage.headers("key-out") === "val-out") } - @Test - def shouldCreateRequestMessageFromInMessageWithAdditionalHeader = { + test("mustCreateRequestMessageFromInMessageWithAdditionalHeader") { val m = sampleInOnly.toRequestMessage(Map("x" -> "y")) assert(m === Message("test-in", Map("key-in" -> "val-in", "x" -> "y"))) } - @Test - def shouldCreateResponseMessageFromInMessageWithAdditionalHeader = { + test("mustCreateResponseMessageFromInMessageWithAdditionalHeader") { val m = sampleInOnly.toResponseMessage(Map("x" -> "y")) assert(m === Message("test-in", Map("key-in" -> "val-in", "x" -> "y"))) } - @Test - def shouldCreateResponseMessageFromOutMessageWithAdditionalHeader = { + test("mustCreateResponseMessageFromOutMessageWithAdditionalHeader") { val m = sampleInOut.toResponseMessage(Map("x" -> "y")) assert(m === Message("test-out", Map("key-out" -> "val-out", "x" -> "y"))) } - @Test - def shouldCreateFailureMessageFromExceptionAndInMessageWithAdditionalHeader = { + test("mustCreateFailureMessageFromExceptionAndInMessageWithAdditionalHeader") { val e1 = sampleInOnly e1.setException(new Exception("test1")) assert(e1.toFailureMessage.cause.getMessage === "test1") @@ -98,8 +99,7 @@ class CamelExchangeAdapterTest extends JUnitSuite { assert(headers("x") === "y") } - @Test - def shouldCreateFailureMessageFromExceptionAndOutMessageWithAdditionalHeader = { + test("mustCreateFailureMessageFromExceptionAndOutMessageWithAdditionalHeader") { val e1 = sampleInOut e1.setException(new Exception("test2")) assert(e1.toFailureMessage.cause.getMessage === "test2") @@ -112,7 +112,7 @@ class CamelExchangeAdapterTest extends JUnitSuite { private def sampleInOut = sampleExchange(ExchangePattern.InOut) private def sampleExchange(pattern: ExchangePattern) = { - val exchange = new DefaultExchange(new DefaultCamelContext) + val exchange = new DefaultExchange(camel.context) exchange.getIn.setBody("test-in") exchange.getOut.setBody("test-out") exchange.getIn.setHeader("key-in", "val-in") diff --git a/akka-camel/src/test/scala/akka/camel/CamelMessageAdapterTest.scala b/akka-camel/src/test/scala/akka/camel/CamelMessageAdapterTest.scala deleted file mode 100644 index 3a1199bfc1..0000000000 --- a/akka-camel/src/test/scala/akka/camel/CamelMessageAdapterTest.scala +++ /dev/null @@ -1,40 +0,0 @@ -package akka.camel - -import org.apache.camel.impl.DefaultMessage -import org.junit.Test -import org.scalatest.junit.JUnitSuite - -class CamelMessageAdapterTest extends JUnitSuite { - import CamelMessageConversion.toMessageAdapter - - @Test - def shouldOverwriteBodyAndAddHeader = { - val cm = sampleMessage.fromMessage(Message("blah", Map("key" -> "baz"))) - assert(cm.getBody === "blah") - assert(cm.getHeader("foo") === "bar") - assert(cm.getHeader("key") === "baz") - } - - @Test - def shouldCreateMessageWithBodyAndHeader = { - val m = sampleMessage.toMessage - assert(m.body === "test") - assert(m.headers("foo") === "bar") - } - - @Test - def shouldCreateMessageWithBodyAndHeaderAndCustomHeader = { - val m = sampleMessage.toMessage(Map("key" -> "baz")) - assert(m.body === "test") - assert(m.headers("foo") === "bar") - assert(m.headers("key") === "baz") - } - - private[camel] def sampleMessage = { - val message = new DefaultMessage - message.setBody("test") - message.setHeader("foo", "bar") - message - } - -} diff --git a/akka-camel/src/test/scala/akka/camel/CamelMessageTest.scala b/akka-camel/src/test/scala/akka/camel/CamelMessageTest.scala new file mode 100644 index 0000000000..c5dfd01d00 --- /dev/null +++ b/akka-camel/src/test/scala/akka/camel/CamelMessageTest.scala @@ -0,0 +1,46 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + +package akka.camel + +import org.apache.camel.impl.{ DefaultExchange, DefaultMessage } +import akka.camel.TestSupport.SharedCamelSystem +import org.scalatest.matchers.MustMatchers +import org.scalatest.WordSpec + +//TODO merge it with MessageScalaTest +class CamelMessageTest extends MustMatchers with WordSpec with SharedCamelSystem { + + "CamelMessage" must { + + "overwrite body and add header" in { + val msg = sampleMessage + CamelMessage("blah", Map("key" -> "baz")).copyContentTo(msg) + assert(msg.getBody === "blah") + assert(msg.getHeader("foo") === "bar") + assert(msg.getHeader("key") === "baz") + } + + "create message with body and header" in { + val m = CamelMessage.from(sampleMessage) + assert(m.body === "test") + assert(m.headers("foo") === "bar") + } + + "create message with body and header and custom header" in { + val m = CamelMessage.from(sampleMessage, Map("key" -> "baz")) + assert(m.body === "test") + assert(m.headers("foo") === "bar") + assert(m.headers("key") === "baz") + } + } + + private[camel] def sampleMessage = { + val message = new DefaultMessage + message.setBody("test") + message.setHeader("foo", "bar") + message.setExchange(new DefaultExchange(camel.context)) + message + } +} diff --git a/akka-camel/src/test/scala/akka/camel/CamelServiceManagerTest.scala b/akka-camel/src/test/scala/akka/camel/CamelServiceManagerTest.scala deleted file mode 100644 index 3db2b1fe54..0000000000 --- a/akka-camel/src/test/scala/akka/camel/CamelServiceManagerTest.scala +++ /dev/null @@ -1,62 +0,0 @@ -package akka.camel - -import org.scalatest.{ BeforeAndAfterAll, WordSpec } -import org.scalatest.matchers.MustMatchers - -import akka.actor.Actor - -/** - * @author Martin Krasser - */ -class CamelServiceManagerTest extends WordSpec with BeforeAndAfterAll with MustMatchers { - - override def afterAll = { - CamelServiceManager.stopCamelService - Actor.registry.local.shutdownAll - } - - "A CamelServiceManager" when { - "the startCamelService method been has been called" must { - "have registered the started CamelService instance" in { - val service = CamelServiceManager.startCamelService - CamelServiceManager.mandatoryService must be theSameInstanceAs (service) - } - } - "the stopCamelService method been has been called" must { - "have unregistered the current CamelService instance" in { - val service = CamelServiceManager.stopCamelService - CamelServiceManager.service must be(None) - } - } - } - - "A CamelServiceManager" when { - val service = CamelServiceFactory.createCamelService - "a CamelService instance has been started externally" must { - "have registered the started CamelService instance" in { - service.start - CamelServiceManager.mandatoryService must be theSameInstanceAs (service) - } - } - "the current CamelService instance has been stopped externally" must { - "have unregistered the current CamelService instance" in { - service.stop - CamelServiceManager.service must be(None) - } - } - } - - "A CamelServiceManager" when { - "a CamelService has been started" must { - "not allow further CamelService instances to be started" in { - CamelServiceManager.startCamelService - intercept[IllegalStateException] { CamelServiceManager.startCamelService } - } - } - "a CamelService has been stopped" must { - "only allow the current CamelService instance to be stopped" in { - intercept[IllegalStateException] { CamelServiceFactory.createCamelService.stop } - } - } - } -} diff --git a/akka-camel/src/test/scala/akka/camel/CamelTestSupport.scala b/akka-camel/src/test/scala/akka/camel/CamelTestSupport.scala deleted file mode 100644 index 01247ced2c..0000000000 --- a/akka-camel/src/test/scala/akka/camel/CamelTestSupport.scala +++ /dev/null @@ -1,78 +0,0 @@ -package akka.camel - -import java.util.concurrent.CountDownLatch - -import collection.mutable.Buffer - -import akka.actor.Actor - -object CamelTestSupport { - type Handler = PartialFunction[Any, Any] - - trait TestActor extends Actor { - def receive = { - case msg ⇒ { - handler(msg) - } - } - - def handler: Handler - } - - class Tester1 extends TestActor with Retain with Countdown { - def handler = retain andThen countdown - } - - class Tester2 extends TestActor with Respond { - def handler = respond - } - - class Tester3 extends TestActor with Noop { - def handler = noop - } - - trait Countdown { this: Actor ⇒ - var latch: CountDownLatch = new CountDownLatch(0) - def countdown: Handler = { - case SetExpectedMessageCount(num) ⇒ { - latch = new CountDownLatch(num) - sender ! latch - } - case msg ⇒ latch.countDown - } - } - - trait Respond { this: Actor ⇒ - def respond: Handler = { - case msg: Message ⇒ sender ! response(msg) - } - - def response(msg: Message): Any = "Hello %s" format msg.body - } - - trait Retain { this: Actor ⇒ - val messages = Buffer[Any]() - - def retain: Handler = { - case GetRetainedMessage ⇒ sender ! messages.last - case GetRetainedMessages(p) ⇒ sender ! messages.filter(p).toList - case msg ⇒ { - messages += msg - msg - } - } - } - - trait Noop { this: Actor ⇒ - def noop: Handler = { - case msg ⇒ msg - } - } - - case class SetExpectedMessageCount(num: Int) - case class GetRetainedMessage() - case class GetRetainedMessages(p: Any ⇒ Boolean) { - def this() = this(_ ⇒ true) - } -} - diff --git a/akka-camel/src/test/scala/akka/camel/ConsumerIntegrationTest.scala b/akka-camel/src/test/scala/akka/camel/ConsumerIntegrationTest.scala new file mode 100644 index 0000000000..46070337c7 --- /dev/null +++ b/akka-camel/src/test/scala/akka/camel/ConsumerIntegrationTest.scala @@ -0,0 +1,171 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + +package akka.camel + +import akka.actor._ +import org.scalatest.matchers.MustMatchers +import akka.util.duration._ +import TestSupport._ +import org.scalatest.WordSpec +import org.apache.camel.model.RouteDefinition +import org.apache.camel.builder.Builder +import org.apache.camel.{ FailedToCreateRouteException, CamelExecutionException } +import java.util.concurrent.{ ExecutionException, TimeUnit, TimeoutException } +import akka.testkit.TestLatch +import akka.dispatch.Await + +class ConsumerIntegrationTest extends WordSpec with MustMatchers with NonSharedCamelSystem { + private val defaultTimeout = 10 + "Consumer must throw FailedToCreateRouteException, while awaiting activation, if endpoint is invalid" in { + val actorRef = system.actorOf(Props(new TestActor(uri = "some invalid uri"))) + + intercept[FailedToCreateRouteException] { + camel.awaitActivation(actorRef, timeout = defaultTimeout seconds) + } + } + + "Consumer must support in-out messaging" in { + start(new Consumer { + def endpointUri = "direct:a1" + def receive = { + case m: CamelMessage ⇒ sender ! "received " + m.bodyAs[String] + } + }) + camel.sendTo("direct:a1", msg = "some message") must be("received some message") + } + + "Consumer must time-out if consumer is slow" in { + val SHORT_TIMEOUT = 10 millis + val LONG_WAIT = 200 millis + + start(new Consumer { + override def replyTimeout = SHORT_TIMEOUT + + def endpointUri = "direct:a3" + def receive = { case _ ⇒ { Thread.sleep(LONG_WAIT.toMillis); sender ! "done" } } + }) + + val exception = intercept[CamelExecutionException] { + camel.sendTo("direct:a3", msg = "some msg 3") + } + exception.getCause.getClass must be(classOf[TimeoutException]) + } + + "Consumer must process messages even after actor restart" in { + val restarted = TestLatch() + val consumer = start(new Consumer { + def endpointUri = "direct:a2" + + def receive = { + case "throw" ⇒ throw new Exception + case m: CamelMessage ⇒ sender ! "received " + m.bodyAs[String] + } + + override def postRestart(reason: Throwable) { + restarted.countDown() + } + }) + consumer ! "throw" + Await.ready(restarted, defaultTimeout seconds) + + val response = camel.sendTo("direct:a2", msg = "xyz") + response must be("received xyz") + } + + "Consumer must unregister itself when stopped" in { + val consumer = start(new TestActor()) + camel.awaitActivation(consumer, defaultTimeout seconds) + + camel.routeCount must be > (0) + + system.stop(consumer) + camel.awaitDeactivation(consumer, defaultTimeout seconds) + + camel.routeCount must be(0) + } + + "Error passing consumer supports error handling through route modification" in { + start(new ErrorThrowingConsumer("direct:error-handler-test") with ErrorPassing { + override def onRouteDefinition(rd: RouteDefinition) = { + rd.onException(classOf[Exception]).handled(true).transform(Builder.exceptionMessage).end + } + }) + camel.sendTo("direct:error-handler-test", msg = "hello") must be("error: hello") + } + + "Error passing consumer supports redelivery through route modification" in { + start(new FailingOnceConsumer("direct:failing-once-concumer") with ErrorPassing { + override def onRouteDefinition(rd: RouteDefinition) = { + rd.onException(classOf[Exception]).maximumRedeliveries(1).end + } + }) + camel.sendTo("direct:failing-once-concumer", msg = "hello") must be("accepted: hello") + } + + "Consumer supports manual Ack" in { + start(new ManualAckConsumer() { + def endpointUri = "direct:manual-ack" + def receive = { case _ ⇒ sender ! Ack } + }) + camel.template.asyncSendBody("direct:manual-ack", "some message").get(defaultTimeout, TimeUnit.SECONDS) must be(null) //should not timeout + } + + "Consumer handles manual Ack failure" in { + val someException = new Exception("e1") + start(new ManualAckConsumer() { + def endpointUri = "direct:manual-ack" + def receive = { case _ ⇒ sender ! Failure(someException) } + }) + + intercept[ExecutionException] { + camel.template.asyncSendBody("direct:manual-ack", "some message").get(defaultTimeout, TimeUnit.SECONDS) + }.getCause.getCause must be(someException) + } + + "Consumer should time-out, if manual Ack not received within replyTimeout and should give a human readable error message" in { + start(new ManualAckConsumer() { + override def replyTimeout = 10 millis + def endpointUri = "direct:manual-ack" + def receive = { case _ ⇒ } + }) + + intercept[ExecutionException] { + camel.template.asyncSendBody("direct:manual-ack", "some message").get(defaultTimeout, TimeUnit.SECONDS) + }.getCause.getCause.getMessage must include("Failed to get Ack") + } +} + +class ErrorThrowingConsumer(override val endpointUri: String) extends Consumer { + def receive = { + case msg: CamelMessage ⇒ throw new Exception("error: %s" format msg.body) + } +} + +class FailingOnceConsumer(override val endpointUri: String) extends Consumer { + + def receive = { + case msg: CamelMessage ⇒ + if (msg.headerAs[Boolean]("CamelRedelivered").getOrElse(false)) + sender ! ("accepted: %s" format msg.body) + else + throw new Exception("rejected: %s" format msg.body) + } +} + +class TestActor(uri: String = "file://target/abcde") extends Consumer { + def endpointUri = uri + def receive = { case _ ⇒ /* do nothing */ } +} + +trait ErrorPassing { + self: Actor ⇒ + final override def preRestart(reason: Throwable, message: Option[Any]) { + sender ! Failure(reason) + } +} + +trait ManualAckConsumer extends Consumer { + override def autoack = false +} diff --git a/akka-camel/src/test/scala/akka/camel/ConsumerJavaTest.scala b/akka-camel/src/test/scala/akka/camel/ConsumerJavaTest.scala index 9f17437159..5685db2cea 100644 --- a/akka-camel/src/test/scala/akka/camel/ConsumerJavaTest.scala +++ b/akka-camel/src/test/scala/akka/camel/ConsumerJavaTest.scala @@ -1,5 +1,9 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + package akka.camel import org.scalatest.junit.JUnitSuite -class ConsumerJavaTest extends ConsumerJavaTestBase with JUnitSuite +class ConsumerJavaTest extends ConsumerJavaTestBase with JUnitSuite \ No newline at end of file diff --git a/akka-camel/src/test/scala/akka/camel/ConsumerPublishRequestorTest.scala b/akka-camel/src/test/scala/akka/camel/ConsumerPublishRequestorTest.scala deleted file mode 100644 index 1c1fb273fb..0000000000 --- a/akka-camel/src/test/scala/akka/camel/ConsumerPublishRequestorTest.scala +++ /dev/null @@ -1,61 +0,0 @@ -package akka.camel - -import java.util.concurrent.{ CountDownLatch, TimeUnit } - -import org.junit.{ Before, After, Test } -import org.scalatest.junit.JUnitSuite - -import akka.actor._ -import akka.actor.Actor._ -import akka.camel.CamelTestSupport.{ SetExpectedMessageCount ⇒ SetExpectedTestMessageCount, _ } -import akka.dispatch.Await - -class ConsumerPublishRequestorTest extends JUnitSuite { - import ConsumerPublishRequestorTest._ - - var publisher: ActorRef = _ - var requestor: ActorRef = _ - var consumer: LocalActorRef = _ - - @Before - def setUp{ - publisher = actorOf(Props(new ConsumerPublisherMock) - requestor = actorOf(Props(new ConsumerPublishRequestor) - requestor ! InitPublishRequestor(publisher) - consumer = actorOf(Props(new Actor with Consumer { - def endpointUri = "mock:test" - protected def receive = null - }).asInstanceOf[LocalActorRef] - } - - @After - def tearDown = { - Actor.registry.removeListener(requestor); - Actor.registry.local.shutdownAll - } - - @Test - def shouldReceiveOneConsumerRegisteredEvent = { - val latch = Await.result((publisher ? SetExpectedTestMessageCount(1)).mapTo[CountDownLatch], 5 seconds) - requestor ! ActorRegistered(consumer.address, consumer) - assert(latch.await(5000, TimeUnit.MILLISECONDS)) - assert(Await.result(publisher ? GetRetainedMessage, 5 seconds) === - ConsumerActorRegistered(consumer, consumer.underlyingActorInstance.asInstanceOf[Consumer])) - } - - @Test - def shouldReceiveOneConsumerUnregisteredEvent = { - val latch = Await.result((publisher ? SetExpectedTestMessageCount(1)).mapTo[CountDownLatch], 5 seconds) - requestor ! ActorUnregistered(consumer.address, consumer) - assert(latch.await(5000, TimeUnit.MILLISECONDS)) - assert(Await.result(publisher ? GetRetainedMessage, 5 seconds) === - ConsumerActorUnregistered(consumer, consumer.underlyingActorInstance.asInstanceOf[Consumer])) - } -} - -object ConsumerPublishRequestorTest { - class ConsumerPublisherMock extends TestActor with Retain with Countdown { - def handler = retain andThen countdown - } -} - diff --git a/akka-camel/src/test/scala/akka/camel/ConsumerRegisteredTest.scala b/akka-camel/src/test/scala/akka/camel/ConsumerRegisteredTest.scala deleted file mode 100644 index 2289b5a3d4..0000000000 --- a/akka-camel/src/test/scala/akka/camel/ConsumerRegisteredTest.scala +++ /dev/null @@ -1,72 +0,0 @@ -package akka.camel - -import org.junit.Test -import org.scalatest.junit.JUnitSuite -import akka.actor.{ ActorRef, Actor, LocalActorRef } - -class ConsumerRegisteredTest extends JUnitSuite { - import ConsumerRegisteredTest._ - - @Test - def shouldCreateSomeNonBlockingPublishRequestFromConsumer = { - val c = Actor.actorOf(Props[ConsumerActor1] - val event = ConsumerActorRegistered.eventFor(c) - assert(event === Some(ConsumerActorRegistered(c, consumerOf(c)))) - } - - @Test - def shouldCreateSomeBlockingPublishRequestFromConsumer = { - val c = Actor.actorOf(Props[ConsumerActor2] - val event = ConsumerActorRegistered.eventFor(c) - assert(event === Some(ConsumerActorRegistered(c, consumerOf(c)))) - } - - @Test - def shouldCreateNoneFromConsumer = { - val event = ConsumerActorRegistered.eventFor(Actor.actorOf(Props[PlainActor]) - assert(event === None) - } - - @Test - def shouldCreateSomeNonBlockingPublishRequestFromUntypedConsumer = { - val uc = Actor.actorOf(classOf[SampleUntypedConsumer]) - val event = ConsumerActorRegistered.eventFor(uc) - assert(event === Some(ConsumerActorRegistered(uc, consumerOf(uc)))) - } - - @Test - def shouldCreateSomeBlockingPublishRequestFromUntypedConsumer = { - val uc = Actor.actorOf(classOf[SampleUntypedConsumerBlocking]) - val event = ConsumerActorRegistered.eventFor(uc) - assert(event === Some(ConsumerActorRegistered(uc, consumerOf(uc)))) - } - - @Test - def shouldCreateNoneFromUntypedConsumer = { - val a = Actor.actorOf(classOf[SampleUntypedActor]) - val event = ConsumerActorRegistered.eventFor(a) - assert(event === None) - } - - private def consumerOf(ref: ActorRef) = ref match { - case l: LocalActorRef ⇒ l.underlyingActorInstance.asInstanceOf[Consumer] - case _ ⇒ null: Consumer - } -} - -object ConsumerRegisteredTest { - class ConsumerActor1 extends Actor with Consumer { - def endpointUri = "mock:test1" - protected def receive = null - } - - class ConsumerActor2 extends Actor with Consumer { - def endpointUri = "mock:test2" - override def blocking = true - protected def receive = null - } - - class PlainActor extends Actor { - protected def receive = null - } -} diff --git a/akka-camel/src/test/scala/akka/camel/ConsumerScalaTest.scala b/akka-camel/src/test/scala/akka/camel/ConsumerScalaTest.scala deleted file mode 100644 index 4382f9a2a6..0000000000 --- a/akka-camel/src/test/scala/akka/camel/ConsumerScalaTest.scala +++ /dev/null @@ -1,295 +0,0 @@ -package akka.camel - -import java.util.concurrent.{ TimeoutException, CountDownLatch, TimeUnit } - -import org.apache.camel.{ AsyncProcessor, AsyncCallback, CamelExecutionException } -import org.apache.camel.builder.Builder -import org.apache.camel.component.direct.DirectEndpoint -import org.apache.camel.model.RouteDefinition -import org.scalatest.{ BeforeAndAfterAll, WordSpec } -import org.scalatest.matchers.MustMatchers - -import akka.actor.Actor._ -import akka.actor._ - -/** - * @author Martin Krasser - */ -class ConsumerScalaTest extends WordSpec with BeforeAndAfterAll with MustMatchers { - import CamelContextManager.mandatoryContext - import CamelContextManager.mandatoryTemplate - import ConsumerScalaTest._ - - var service: CamelService = null - - override protected def beforeAll = { - registry.local.shutdownAll - service = CamelServiceFactory.createCamelService - // register test consumer before registering the publish requestor - // and before starting the CamelService (registry is scanned for consumers) - actorOf(Props(new TestConsumer("direct:publish-test-1")) - service.registerPublishRequestor - service.awaitEndpointActivation(1) { - service.start - } must be(true) - } - - override protected def afterAll = { - service.stop - registry.local.shutdownAll - } - - "A responding consumer" when { - var consumer: ActorRef = null - "started before starting the CamelService" must { - "support an in-out message exchange via its endpoint" in { - mandatoryTemplate.requestBody("direct:publish-test-1", "msg1") must equal("received msg1") - } - } - "not started" must { - "not have an associated endpoint in the CamelContext" in { - CamelContextManager.mandatoryContext.hasEndpoint("direct:publish-test-2") must be(null) - } - } - "started" must { - "support an in-out message exchange via its endpoint" in { - service.awaitEndpointActivation(1) { - consumer = actorOf(Props(new TestConsumer("direct:publish-test-2")) - } must be(true) - mandatoryTemplate.requestBody("direct:publish-test-2", "msg2") must equal("received msg2") - } - "have an associated endpoint in the CamelContext" in { - CamelContextManager.mandatoryContext.hasEndpoint("direct:publish-test-2") must not be (null) - } - } - "stopped" must { - "not support an in-out message exchange via its endpoint" in { - service.awaitEndpointDeactivation(1) { - consumer.stop - } must be(true) - intercept[CamelExecutionException] { - mandatoryTemplate.requestBody("direct:publish-test-2", "msg2") - } - } - } - } - - "A responding, untyped consumer" when { - var consumer: ActorRef = null - "started" must { - "support an in-out message exchange via its endpoint" in { - service.awaitEndpointActivation(1) { - consumer = Actor.actorOf(classOf[SampleUntypedConsumer]) - } must be(true) - mandatoryTemplate.requestBodyAndHeader("direct:test-untyped-consumer", "x", "test", "y") must equal("x y") - } - } - "stopped" must { - "not support an in-out message exchange via its endpoint" in { - service.awaitEndpointDeactivation(1) { - consumer.stop - } must be(true) - intercept[CamelExecutionException] { - mandatoryTemplate.sendBodyAndHeader("direct:test-untyped-consumer", "blah", "test", "blub") - } - } - } - } - - "A non-responding, blocking consumer" when { - "receiving an in-out message exchange" must { - "lead to a TimeoutException" in { - service.awaitEndpointActivation(1) { - actorOf(Props(creator = () ⇒ new TestBlocker("direct:publish-test-5"))) - } must be(true) - - try { - mandatoryTemplate.requestBody("direct:publish-test-5", "msg3") - fail("expected TimoutException not thrown") - } catch { - case e ⇒ { - assert(e.getCause.isInstanceOf[TimeoutException]) - } - } - } - } - } - - "A responding, blocking consumer" when { - "activated with a custom error handler" must { - "handle thrown exceptions by generating a custom response" in { - service.awaitEndpointActivation(1) { - actorOf(Props[ErrorHandlingConsumer] - } must be(true) - mandatoryTemplate.requestBody("direct:error-handler-test", "hello") must equal("error: hello") - - } - } - "activated with a custom redelivery handler" must { - "handle thrown exceptions by redelivering the initial message" in { - service.awaitEndpointActivation(1) { - actorOf(Props[RedeliveringConsumer] - } must be(true) - mandatoryTemplate.requestBody("direct:redelivery-test", "hello") must equal("accepted: hello") - - } - } - } - - "An non auto-acknowledging consumer" when { - "started" must { - "must support acknowledgements on system level" in { - - var consumer: ActorRef = null - - service.awaitEndpointActivation(1) { - consumer = actorOf(Props(new TestAckConsumer("direct:system-ack-test")) - } must be(true) - - val endpoint = mandatoryContext.getEndpoint("direct:system-ack-test", classOf[DirectEndpoint]) - val producer = endpoint.createProducer.asInstanceOf[AsyncProcessor] - val exchange = endpoint.createExchange - - val latch = new CountDownLatch(1) - val handler = new AsyncCallback { - def done(doneSync: Boolean) = { - doneSync must be(false) - latch.countDown - } - } - - exchange.getIn.setBody("test") - producer.process(exchange, handler) - - latch.await(5, TimeUnit.SECONDS) must be(true) - consumer.stop - } - } - } - - "A supervised consumer" must { - "be able to reply during receive" in { - val consumer = Actor.actorOf(Props(new SupervisedConsumer("reply-channel-test-1")) - (consumer ? "succeed").get must equal("ok") - } - - "be able to reply on failure during preRestart" in { - val consumer = Actor.actorOf(Props(new SupervisedConsumer("reply-channel-test-2")) - val supervisor = Supervisor( - SupervisorConfig( - OneForOneStrategy(List(classOf[Exception]), 2, 10000), - Supervise(consumer, Permanent) :: Nil)) - - val latch = new CountDownLatch(1) - val sender = Actor.actorOf(Props(new Sender("pr", latch)) - - consumer.!("fail")(Some(sender)) - latch.await(5, TimeUnit.SECONDS) must be(true) - } - - "be able to reply on failure during postStop" in { - val consumer = Actor.actorOf(Props(new SupervisedConsumer("reply-channel-test-3"))) - val supervisor = Supervisor( - SupervisorConfig( - OneForOneStrategy(List(classOf[Exception]), Some(0)), - Supervise(consumer, Temporary) :: Nil)) - - val latch = new CountDownLatch(1) - val sender = Actor.actorOf(Props(new Sender("ps", latch)) - - consumer.!("fail")(Some(sender)) - latch.await(5, TimeUnit.SECONDS) must be(true) - } - } -} - -object ConsumerScalaTest { - trait BlockingConsumer extends Consumer { self: Actor ⇒ - override def blocking = true - } - - class TestConsumer(uri: String) extends Actor with Consumer { - def endpointUri = uri - protected def receive = { - case msg: Message ⇒ sender ! "received %s" format msg.body - } - } - - class TestBlocker(uri: String) extends Actor with BlockingConsumer { - def endpointUri = uri - protected def receive = { - case msg: Message ⇒ { /* do not reply */ } - } - } - - class TestAckConsumer(uri: String) extends Actor with Consumer { - def endpointUri = uri - override def autoack = false - protected def receive = { - case msg: Message ⇒ sender ! Ack - } - } - - class ErrorHandlingConsumer extends Actor with BlockingConsumer { - def endpointUri = "direct:error-handler-test" - - onRouteDefinition { rd: RouteDefinition ⇒ - rd.onException(classOf[Exception]).handled(true).transform(Builder.exceptionMessage).end - } - - protected def receive = { - case msg: Message ⇒ throw new Exception("error: %s" format msg.body) - } - } - - class SupervisedConsumer(name: String) extends Actor with Consumer { - def endpointUri = "direct:%s" format name - - protected def receive = { - case "fail" ⇒ { throw new Exception("test") } - case "succeed" ⇒ sender ! "ok" - } - - override def preRestart(reason: scala.Throwable, msg: Option[Any]) { - sender.tell("pr") - } - - override def postStop { - sender.tell("ps") - } - } - - class Sender(expected: String, latch: CountDownLatch) extends Actor { - def receive = { - case msg if (msg == expected) ⇒ latch.countDown - case _ ⇒ {} - } - } - - class RedeliveringConsumer extends Actor with BlockingConsumer { - def endpointUri = "direct:redelivery-test" - - onRouteDefinition { rd: RouteDefinition ⇒ - rd.onException(classOf[Exception]).maximumRedeliveries(1).end - } - - // - // first message to this actor is not valid and will be rejected - // - - var valid = false - - protected def receive = { - case msg: Message ⇒ try { - respondTo(msg) - } finally { - valid = true - } - } - - private def respondTo(msg: Message) = - if (valid) sender ! ("accepted: %s" format msg.body) - else throw new Exception("rejected: %s" format msg.body) - - } -} diff --git a/akka-camel/src/test/scala/akka/camel/DefaultCamelTest.scala b/akka-camel/src/test/scala/akka/camel/DefaultCamelTest.scala new file mode 100644 index 0000000000..abc50b49e8 --- /dev/null +++ b/akka-camel/src/test/scala/akka/camel/DefaultCamelTest.scala @@ -0,0 +1,59 @@ +/** + * Copyright (C) 2009when2012 Typesafe Inc. + */ + +package akka.camel + +import akka.camel.TestSupport.SharedCamelSystem +import internal.DefaultCamel +import org.scalatest.matchers.MustMatchers +import org.scalatest.mock.MockitoSugar +import akka.actor.ActorSystem +import org.apache.camel.{ CamelContext, ProducerTemplate } +import org.scalatest.WordSpec +import akka.event.LoggingAdapter + +class DefaultCamelTest extends WordSpec with SharedCamelSystem with MustMatchers with MockitoSugar { + + import org.mockito.Mockito.{ when, verify } + + def camelWitMocks = new DefaultCamel(mock[ActorSystem]) { + override val log = mock[LoggingAdapter] + override lazy val template = mock[ProducerTemplate] + override lazy val context = mock[CamelContext] + } + + "during shutdown, when both context and template fail to shutdown" when { + val camel = camelWitMocks + + when(camel.context.stop()) thenThrow new RuntimeException("context") + when(camel.template.stop()) thenThrow new RuntimeException("template") + val exception = intercept[RuntimeException] { + camel.shutdown() + } + + "throws exception thrown by context.stop()" in { + exception.getMessage() must be("context"); + } + + "tries to stop both template and context" in { + verify(camel.template).stop() + verify(camel.context).stop() + } + + } + + "during start, if template fails to start, it will stop the context" in { + val camel = camelWitMocks + + when(camel.template.start()) thenThrow new RuntimeException + + intercept[RuntimeException] { + camel.start + } + + verify(camel.context).stop() + + } + +} \ No newline at end of file diff --git a/akka-camel/src/test/scala/akka/camel/MessageJavaTest.scala b/akka-camel/src/test/scala/akka/camel/MessageJavaTest.scala index 3c95887eb4..9d56642b9f 100644 --- a/akka-camel/src/test/scala/akka/camel/MessageJavaTest.scala +++ b/akka-camel/src/test/scala/akka/camel/MessageJavaTest.scala @@ -1,3 +1,7 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + package akka.camel import org.scalatest.junit.JUnitSuite diff --git a/akka-camel/src/test/scala/akka/camel/MessageScalaTest.scala b/akka-camel/src/test/scala/akka/camel/MessageScalaTest.scala index 7e8145ed4a..81fa0678ed 100644 --- a/akka-camel/src/test/scala/akka/camel/MessageScalaTest.scala +++ b/akka-camel/src/test/scala/akka/camel/MessageScalaTest.scala @@ -1,94 +1,78 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + package akka.camel import java.io.InputStream import org.apache.camel.NoTypeConversionAvailableException -import org.junit.Assert._ -import org.junit.Test +import akka.camel.TestSupport.{ SharedCamelSystem, MessageSugar } +import org.scalatest.FunSuite +import org.scalatest.matchers.MustMatchers -import org.scalatest.BeforeAndAfterAll -import org.scalatest.junit.JUnitSuite - -class MessageScalaTest extends JUnitSuite with BeforeAndAfterAll { - override protected def beforeAll = CamelContextManager.init - - @Test - def shouldConvertDoubleBodyToString = { - assertEquals("1.4", Message(1.4).bodyAs[String]) +class MessageScalaTest extends FunSuite with MustMatchers with SharedCamelSystem with MessageSugar { + implicit def camelContext = camel.context + test("mustConvertDoubleBodyToString") { + Message(1.4).bodyAs[String] must be("1.4") } - @Test - def shouldThrowExceptionWhenConvertingDoubleBodyToInputStream { + test("mustThrowExceptionWhenConvertingDoubleBodyToInputStream") { intercept[NoTypeConversionAvailableException] { Message(1.4).bodyAs[InputStream] } } - @Test - def shouldReturnDoubleHeader = { + test("mustReturnDoubleHeader") { val message = Message("test", Map("test" -> 1.4)) - assertEquals(1.4, message.header("test")) + message.header("test").get must be(1.4) } - @Test - def shouldConvertDoubleHeaderToString = { + test("mustConvertDoubleHeaderToString") { val message = Message("test", Map("test" -> 1.4)) - assertEquals("1.4", message.headerAs[String]("test")) + message.headerAs[String]("test").get must be("1.4") } - @Test - def shouldReturnSubsetOfHeaders = { + test("mustReturnSubsetOfHeaders") { val message = Message("test", Map("A" -> "1", "B" -> "2")) - assertEquals(Map("B" -> "2"), message.headers(Set("B"))) + message.headers(Set("B")) must be(Map("B" -> "2")) } - @Test - def shouldTransformBodyAndPreserveHeaders = { - assertEquals( - Message("ab", Map("A" -> "1")), - Message("a", Map("A" -> "1")).transformBody((body: String) ⇒ body + "b")) + test("mustTransformBodyAndPreserveHeaders") { + Message("a", Map("A" -> "1")).mapBody((body: String) ⇒ body + "b") must be(Message("ab", Map("A" -> "1"))) } - @Test - def shouldConvertBodyAndPreserveHeaders = { - assertEquals( - Message("1.4", Map("A" -> "1")), - Message(1.4, Map("A" -> "1")).setBodyAs[String]) + test("mustConvertBodyAndPreserveHeaders") { + Message(1.4, Map("A" -> "1")).withBodyAs[String] must be(Message("1.4", Map("A" -> "1"))) } - @Test - def shouldSetBodyAndPreserveHeaders = { - assertEquals( - Message("test2", Map("A" -> "1")), - Message("test1", Map("A" -> "1")).setBody("test2")) - } - - @Test - def shouldSetHeadersAndPreserveBody = { - assertEquals( - Message("test1", Map("C" -> "3")), - Message("test1", Map("A" -> "1")).setHeaders(Map("C" -> "3"))) + test("mustSetBodyAndPreserveHeaders") { + Message("test1", Map("A" -> "1")).withBody("test2") must be( + Message("test2", Map("A" -> "1"))) } - @Test - def shouldAddHeaderAndPreserveBodyAndHeaders = { - assertEquals( - Message("test1", Map("A" -> "1", "B" -> "2")), - Message("test1", Map("A" -> "1")).addHeader("B" -> "2")) + test("mustSetHeadersAndPreserveBody") { + Message("test1", Map("A" -> "1")).withHeaders(Map("C" -> "3")) must be( + Message("test1", Map("C" -> "3"))) + } - @Test - def shouldAddHeadersAndPreserveBodyAndHeaders = { - assertEquals( - Message("test1", Map("A" -> "1", "B" -> "2")), - Message("test1", Map("A" -> "1")).addHeaders(Map("B" -> "2"))) + test("mustAddHeaderAndPreserveBodyAndHeaders") { + Message("test1", Map("A" -> "1")).addHeader("B" -> "2") must be( + Message("test1", Map("A" -> "1", "B" -> "2"))) + } - @Test - def shouldRemoveHeadersAndPreserveBodyAndRemainingHeaders = { - assertEquals( - Message("test1", Map("A" -> "1")), - Message("test1", Map("A" -> "1", "B" -> "2")).removeHeader("B")) + test("mustAddHeadersAndPreserveBodyAndHeaders") { + Message("test1", Map("A" -> "1")).addHeaders(Map("B" -> "2")) must be( + Message("test1", Map("A" -> "1", "B" -> "2"))) + + } + + test("mustRemoveHeadersAndPreserveBodyAndRemainingHeaders") { + Message("test1", Map("A" -> "1", "B" -> "2")).withoutHeader("B") must be( + Message("test1", Map("A" -> "1"))) + } } diff --git a/akka-camel/src/test/scala/akka/camel/ProducerFeatureTest.scala b/akka-camel/src/test/scala/akka/camel/ProducerFeatureTest.scala index a3c67064f8..983ff8ed0c 100644 --- a/akka-camel/src/test/scala/akka/camel/ProducerFeatureTest.scala +++ b/akka-camel/src/test/scala/akka/camel/ProducerFeatureTest.scala @@ -1,250 +1,282 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + package akka.camel import org.apache.camel.{ Exchange, Processor } import org.apache.camel.builder.RouteBuilder import org.apache.camel.component.mock.MockEndpoint -import org.scalatest.{ GivenWhenThen, BeforeAndAfterEach, BeforeAndAfterAll, FeatureSpec } +import akka.actor._ +import akka.pattern._ +import akka.dispatch.Await +import akka.util.duration._ +import akka.camel.TestSupport.SharedCamelSystem +import org.scalatest._ -import akka.actor.Actor._ -import akka.actor.{ ActorRef, Actor } +/** + * Tests the features of the Camel Producer. + */ +class ProducerFeatureTest extends WordSpec with BeforeAndAfterAll with BeforeAndAfterEach with SharedCamelSystem with GivenWhenThen { -class ProducerFeatureTest extends FeatureSpec with BeforeAndAfterAll with BeforeAndAfterEach with GivenWhenThen { import ProducerFeatureTest._ - override protected def beforeAll = { - Actor.registry.local.shutdownAll - CamelContextManager.init - CamelContextManager.mandatoryContext.addRoutes(new TestRoute) - CamelContextManager.start - } + val camelContext = camel.context + // to make testing equality of messages easier, otherwise the breadcrumb shows up in the result. + camelContext.setUseBreadcrumb(false) - override protected def afterAll = { - CamelContextManager.stop - Actor.registry.local.shutdownAll - } + val timeout = 1 second - override protected def afterEach = { - mockEndpoint.reset - } + override protected def beforeAll { camelContext.addRoutes(new TestRoute(system)) } - feature("Produce a message to a sync Camel route") { + override protected def afterEach { mockEndpoint.reset() } - scenario("produce message and receive normal response") { + "A Producer on a sync Camel route" must { + + "produce a message and receive normal response" in { given("a registered two-way producer") - val producer = actorOf(Props(new TestProducer("direct:producer-test-2", true)) - + val producer = system.actorOf(Props(new TestProducer("direct:producer-test-2", true))) when("a test message is sent to the producer with ?") - val message = Message("test", Map(Message.MessageExchangeId -> "123")) - val result = (producer ? message).get - - then("a normal response should have been returned by the producer") - val expected = Message("received TEST", Map(Message.MessageExchangeId -> "123")) - assert(result === expected) + val message = CamelMessage("test", Map(CamelMessage.MessageExchangeId -> "123")) + val future = producer.ask(message)(timeout) + then("a normal response must have been returned by the producer") + val expected = CamelMessage("received TEST", Map(CamelMessage.MessageExchangeId -> "123")) + Await.result(future, timeout) match { + case result: CamelMessage ⇒ assert(result === expected) + case unexpected ⇒ fail("Actor responded with unexpected message:" + unexpected) + } } - scenario("produce message and receive failure response") { + "produce a message and receive failure response" in { given("a registered two-way producer") - val producer = actorOf(Props(new TestProducer("direct:producer-test-2")) + val producer = system.actorOf(Props(new TestProducer("direct:producer-test-2"))) when("a test message causing an exception is sent to the producer with ?") - val message = Message("fail", Map(Message.MessageExchangeId -> "123")) - val result = (producer ? message).as[Failure] - - then("a failure response should have been returned by the producer") - val expectedFailureText = result.get.cause.getMessage - val expectedHeaders = result.get.headers - assert(expectedFailureText === "failure") - assert(expectedHeaders === Map(Message.MessageExchangeId -> "123")) + val message = CamelMessage("fail", Map(CamelMessage.MessageExchangeId -> "123")) + val future = producer.ask(message)(timeout) + Await.result(future, timeout) match { + case result: Failure ⇒ + then("a failure response must have been returned by the producer") + val expectedFailureText = result.cause.getMessage + val expectedHeaders = result.headers + assert(expectedFailureText === "failure") + assert(expectedHeaders === Map(CamelMessage.MessageExchangeId -> "123")) + case unexpected ⇒ fail("Actor responded with unexpected message:" + unexpected) + } } - scenario("produce message oneway") { + "produce a message oneway" in { given("a registered one-way producer") - val producer = 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 !") mockEndpoint.expectedBodiesReceived("TEST") - producer ! Message("test") + producer ! CamelMessage("test", Map()) - then("the test message should have been sent to mock:mock") - mockEndpoint.assertIsSatisfied + then("the test message must have been sent to mock:mock") + mockEndpoint.assertIsSatisfied() } - scenario("produce message twoway without sender reference") { + "produces message twoway without sender reference" in { given("a registered two-way producer") - val producer = 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 !") mockEndpoint.expectedBodiesReceived("test") - producer ! Message("test") + producer ! CamelMessage("test", Map()) - then("there should be only a warning that there's no sender reference") - mockEndpoint.assertIsSatisfied + then("there must be only a warning that there's no sender reference") + mockEndpoint.assertIsSatisfied() } } - feature("Produce a message to an async Camel route") { + "A Producer on an async Camel route" must { - scenario("produce message and receive normal response") { + "produce message to direct:producer-test-3 and receive normal response" in { given("a registered two-way producer") - val producer = 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 ?") - val message = Message("test", Map(Message.MessageExchangeId -> "123")) - val result = (producer ? message).as[Message].get + val message = CamelMessage("test", Map(CamelMessage.MessageExchangeId -> "123")) + val future = producer.ask(message)(timeout) - then("a normal response should have been returned by the producer") - assert(result.headers(Message.MessageExchangeId) === "123") + Await.result(future, timeout) match { + case result: CamelMessage ⇒ + then("a normal response must have been returned by the producer") + val expected = CamelMessage("received test", Map(CamelMessage.MessageExchangeId -> "123")) + assert(result === expected) + case unexpected ⇒ fail("Actor responded with unexpected message:" + unexpected) + } } - scenario("produce message and receive failure response") { + "produce message to direct:producer-test-3 and receive failure response" in { given("a registered two-way producer") - val producer = 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 ?") - val message = Message("fail", Map(Message.MessageExchangeId -> "123")) - val result = (producer ? message).as[Failure] - - then("a failure response should have been returned by the producer") - val expectedFailureText = result.get.cause.getMessage - val expectedHeaders = result.get.headers - assert(expectedFailureText === "failure") - assert(expectedHeaders === Map(Message.MessageExchangeId -> "123")) + val message = CamelMessage("fail", Map(CamelMessage.MessageExchangeId -> "123")) + val future = producer.ask(message)(timeout) + Await.result(future, timeout) match { + case result: Failure ⇒ + then("a failure response must have been returned by the producer") + val expectedFailureText = result.cause.getMessage + val expectedHeaders = result.headers + assert(expectedFailureText === "failure") + assert(expectedHeaders === Map(CamelMessage.MessageExchangeId -> "123")) + case unexpected ⇒ fail("Actor responded with unexpected message:" + unexpected) + } } - } - feature("Produce a message to a sync Camel route and then forward the response") { - - scenario("produce message, forward normal response to a replying target actor and receive response") { + "produce message, forward normal response of direct:producer-test-2 to a replying target actor and receive response" in { given("a registered two-way producer configured with a forward target") - val target = actorOf(Props[ReplyingForwardTarget] - val producer = actorOf(Props(new TestForwarder("direct:producer-test-2", target)) + val target = system.actorOf(Props[ReplyingForwardTarget]) + val producer = system.actorOf(Props(new TestForwarder("direct:producer-test-2", target))) when("a test message is sent to the producer with ?") - val message = Message("test", Map(Message.MessageExchangeId -> "123")) - val result = (producer ? message).get + val message = CamelMessage("test", Map(CamelMessage.MessageExchangeId -> "123")) + val future = producer.ask(message)(timeout) - then("a normal response should have been returned by the forward target") - val expected = Message("received test", Map(Message.MessageExchangeId -> "123", "test" -> "result")) - assert(result === expected) + Await.result(future, timeout) match { + case result: CamelMessage ⇒ + then("a normal response must have been returned by the forward target") + val expected = CamelMessage("received test", Map(CamelMessage.MessageExchangeId -> "123", "test" -> "result")) + assert(result === expected) + case unexpected ⇒ fail("Actor responded with unexpected message:" + unexpected) + } } - scenario("produce message, forward failure response to a replying target actor and receive 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") - val target = actorOf(Props[ReplyingForwardTarget] - val producer = actorOf(Props(new TestForwarder("direct:producer-test-2", target)) + val target = system.actorOf(Props[ReplyingForwardTarget]) + val producer = system.actorOf(Props(new TestForwarder("direct:producer-test-2", target))) when("a test message causing an exception is sent to the producer with ?") - val message = Message("fail", Map(Message.MessageExchangeId -> "123")) - val result = (producer ? message).as[Failure].get - - then("a failure response should have been returned by the forward target") - val expectedFailureText = result.cause.getMessage - val expectedHeaders = result.headers - assert(expectedFailureText === "failure") - assert(expectedHeaders === Map(Message.MessageExchangeId -> "123", "test" -> "failure")) + val message = CamelMessage("fail", Map(CamelMessage.MessageExchangeId -> "123")) + val future = producer.ask(message)(timeout) + Await.result(future, timeout) match { + case failure: Failure ⇒ + then("a failure response must have been returned by the forward target") + val expectedFailureText = failure.cause.getMessage + val expectedHeaders = failure.headers + assert(expectedFailureText === "failure") + assert(expectedHeaders === Map(CamelMessage.MessageExchangeId -> "123", "test" -> "failure")) + case unexpected ⇒ fail("Actor responded with unexpected message:" + unexpected) + } } - scenario("produce message, forward normal response to a producing target actor and produce 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") - val target = actorOf(Props[ProducingForwardTarget] - val producer = actorOf(Props(new TestForwarder("direct:producer-test-2", target)) + val target = system.actorOf(Props[ProducingForwardTarget]) + val producer = system.actorOf(Props(new TestForwarder("direct:producer-test-2", target))) when("a test message is sent to the producer with !") mockEndpoint.expectedBodiesReceived("received test") - val result = producer.!(Message("test"))(Some(producer)) + producer.tell(CamelMessage("test", Map()), producer) - then("a normal response should have been produced by the forward target") - mockEndpoint.assertIsSatisfied + then("a normal response must have been produced by the forward target") + mockEndpoint.assertIsSatisfied() } - scenario("produce message, forward failure response to a producing target actor and produce response to direct:forward-test-1") { + "produce message, forward failure response to a producing target actor and produce response to direct:forward-test-1" in { given("a registered one-way producer configured with a forward target") - val target = actorOf(Props[ProducingForwardTarget] - val producer = actorOf(Props(new TestForwarder("direct:producer-test-2", target)) + + val target = system.actorOf(Props[ProducingForwardTarget]) + val producer = system.actorOf(Props(new TestForwarder("direct:producer-test-2", target))) when("a test message causing an exception is sent to the producer with !") mockEndpoint.expectedMessageCount(1) mockEndpoint.message(0).body().isInstanceOf(classOf[Failure]) - val result = producer.!(Message("fail"))(Some(producer)) + producer.tell(CamelMessage("fail", Map()), producer) - then("a failure response should have been produced by the forward target") - mockEndpoint.assertIsSatisfied + then("a failure response must have been produced by the forward target") + mockEndpoint.assertIsSatisfied() } - } - feature("Produce a message to an async Camel route and then forward the response") { - - scenario("produce message, forward normal response to a replying target actor and receive 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") - val target = actorOf(Props[ReplyingForwardTarget] - val producer = actorOf(Props(new TestForwarder("direct:producer-test-3", target)) + val target = system.actorOf(Props[ReplyingForwardTarget]) + val producer = system.actorOf(Props(new TestForwarder("direct:producer-test-3", target))) when("a test message is sent to the producer with ?") - val message = Message("test", Map(Message.MessageExchangeId -> "123")) - val result = (producer ? message).as[Message].get + val message = CamelMessage("test", Map(CamelMessage.MessageExchangeId -> "123")) - then("a normal response should have been returned by the forward target") - assert(result.headers(Message.MessageExchangeId) === "123") - assert(result.headers("test") === "result") + val future = producer.ask(message)(timeout) + + then("a normal response must have been returned by the forward target") + Await.result(future, timeout) match { + case message: CamelMessage ⇒ + val expected = CamelMessage("received test", Map(CamelMessage.MessageExchangeId -> "123", "test" -> "result")) + assert(message === expected) + case unexpected ⇒ fail("Actor responded with unexpected message:" + unexpected) + } } - scenario("produce message, forward failure response to a replying target actor and receive 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") - val target = actorOf(Props[ReplyingForwardTarget] - val producer = actorOf(Props(new TestForwarder("direct:producer-test-3", target)) + val target = system.actorOf(Props[ReplyingForwardTarget]) + val producer = system.actorOf(Props(new TestForwarder("direct:producer-test-3", target))) - when("a test message causing an exception is sent to the producer with ?") - val message = Message("fail", Map(Message.MessageExchangeId -> "123")) - val result = (producer ? message).as[Failure] - - then("a failure response should have been returned by the forward target") - val expectedFailureText = result.get.cause.getMessage - val expectedHeaders = result.get.headers - assert(expectedFailureText === "failure") - assert(expectedHeaders === Map(Message.MessageExchangeId -> "123", "test" -> "failure")) + when("a test message causing an exception is sent to the producer with !!") + val message = CamelMessage("fail", Map(CamelMessage.MessageExchangeId -> "123")) + val future = producer.ask(message)(timeout) + Await.result(future, timeout) match { + case failure: Failure ⇒ + then("a failure response must have been returned by the forward target") + val expectedFailureText = failure.cause.getMessage + val expectedHeaders = failure.headers + assert(expectedFailureText === "failure") + assert(expectedHeaders === Map(CamelMessage.MessageExchangeId -> "123", "test" -> "failure")) + case unexpected ⇒ fail("Actor responded with unexpected message:" + unexpected) + } } - scenario("produce message, forward normal response to a producing target actor and produce 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") - val target = actorOf(Props[ProducingForwardTarget] - val producer = actorOf(Props(new TestForwarder("direct:producer-test-3", target)) + val target = system.actorOf(Props[ProducingForwardTarget]) + val producer = system.actorOf(Props(new TestForwarder("direct:producer-test-3", target))) when("a test message is sent to the producer with !") mockEndpoint.expectedBodiesReceived("received test") - val result = producer.!(Message("test"))(Some(producer)) + producer.tell(CamelMessage("test", Map()), producer) - then("a normal response should have been produced by the forward target") - mockEndpoint.assertIsSatisfied + then("a normal response must have been produced by the forward target") + mockEndpoint.assertIsSatisfied() } - scenario("produce message, forward failure response to a producing target actor and produce response to direct:forward-test-1") { + "produce message, forward failure response from direct:producer-test-3 to a producing target actor and produce response to direct:forward-test-1" in { given("a registered one-way producer configured with a forward target") - val target = actorOf(Props[ProducingForwardTarget] - val producer = actorOf(Props(new TestForwarder("direct:producer-test-3", target)) + val target = system.actorOf(Props[ProducingForwardTarget]) + val producer = system.actorOf(Props(new TestForwarder("direct:producer-test-3", target))) when("a test message causing an exception is sent to the producer with !") mockEndpoint.expectedMessageCount(1) mockEndpoint.message(0).body().isInstanceOf(classOf[Failure]) - val result = producer.!(Message("fail"))(Some(producer)) + producer.tell(CamelMessage("fail", Map()), producer) - then("a failure response should have been produced by the forward target") - mockEndpoint.assertIsSatisfied + then("a failure response must have been produced by the forward target") + mockEndpoint.assertIsSatisfied() } } - private def mockEndpoint = CamelContextManager.mandatoryContext.getEndpoint("mock:mock", classOf[MockEndpoint]) + private def mockEndpoint = camel.context.getEndpoint("mock:mock", classOf[MockEndpoint]) } object ProducerFeatureTest { + class TestProducer(uri: String, upper: Boolean = false) extends Actor with Producer { def endpointUri = uri + override protected def receiveBeforeProduce = { - case msg: Message ⇒ if (upper) msg.transformBody { body: String ⇒ body.toUpperCase } else msg + case msg: CamelMessage ⇒ if (upper) msg.mapBody { + body: String ⇒ body.toUpperCase + } + else msg } } class TestForwarder(uri: String, target: ActorRef) extends Actor with Producer { def endpointUri = uri + override protected def receiveAfterProduce = { case msg ⇒ target forward msg } @@ -252,17 +284,22 @@ object ProducerFeatureTest { class TestResponder extends Actor { protected def receive = { - case msg: Message ⇒ msg.body match { - case "fail" ⇒ sender ! Failure(new Exception("failure"), msg.headers) - case _ ⇒ sender ! (msg.transformBody { body: String ⇒ "received %s" format body }) + case msg: CamelMessage ⇒ msg.body match { + case "fail" ⇒ context.sender ! (Failure(new Exception("failure"), msg.headers)) + case _ ⇒ + context.sender ! (msg.mapBody { + body: String ⇒ "received %s" format body + }) } } } class ReplyingForwardTarget extends Actor { protected def receive = { - case msg: Message ⇒ sender ! msg.addHeader("test" -> "result") - case msg: Failure ⇒ sender ! Failure(msg.cause, msg.headers + ("test" -> "failure")) + case msg: CamelMessage ⇒ + context.sender ! (msg.addHeader("test" -> "result")) + case msg: Failure ⇒ + context.sender ! (Failure(msg.cause, msg.headers + ("test" -> "failure"))) } } @@ -270,14 +307,15 @@ object ProducerFeatureTest { def endpointUri = "direct:forward-test-1" } - class TestRoute extends RouteBuilder { - val responder = actorOf(Props[TestResponder] + class TestRoute(system: ActorSystem) extends RouteBuilder { + val responder = system.actorOf(Props[TestResponder], name = "TestResponder") + def configure { from("direct:forward-test-1").to("mock:mock") // for one-way messaging tests from("direct:producer-test-1").to("mock:mock") // for two-way messaging tests (async) - from("direct:producer-test-3").to("actor:uuid:%s" format responder.uuid) + from("direct:producer-test-3").to(responder) // for two-way messaging tests (sync) from("direct:producer-test-2").process(new Processor() { def process(exchange: Exchange) = { @@ -289,4 +327,5 @@ object ProducerFeatureTest { }) } } + } diff --git a/akka-camel/src/test/scala/akka/camel/ProducerRegistryTest.scala b/akka-camel/src/test/scala/akka/camel/ProducerRegistryTest.scala new file mode 100644 index 0000000000..8355906c80 --- /dev/null +++ b/akka-camel/src/test/scala/akka/camel/ProducerRegistryTest.scala @@ -0,0 +1,47 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + +package akka.camel + +import org.scalatest.matchers.MustMatchers +import org.scalatest.WordSpec +import akka.camel.TestSupport.SharedCamelSystem +import akka.util.duration._ +import akka.actor.{ ActorRef, Props } + +class ProducerRegistryTest extends WordSpec with MustMatchers with SharedCamelSystem { + + "A ProducerRegistry" must { + def newEmptyActor: ActorRef = system.actorOf(Props.empty) + def registerProcessorFor(actorRef: ActorRef) = camel.registerProducer(actorRef, "mock:mock")._2 + + "register a started SendProcessor for the producer, which is stopped when the actor is stopped" in { + val actorRef = newEmptyActor + val processor = registerProcessorFor(actorRef) + camel.awaitActivation(actorRef, 1 second) + processor.isStarted must be(true) + system.stop(actorRef) + camel.awaitDeactivation(actorRef, 1 second) + (processor.isStopping || processor.isStopped) must be(true) + } + "remove and stop the SendProcessor if the actorRef is registered" in { + val actorRef = newEmptyActor + val processor = registerProcessorFor(actorRef) + camel.unregisterProducer(actorRef) + (processor.isStopping || processor.isStopped) must be(true) + } + "remove and stop only the SendProcessor for the actorRef that is registered" in { + val actorRef1 = newEmptyActor + val actorRef2 = newEmptyActor + val processor = registerProcessorFor(actorRef2) + // this generates a warning on the activationTracker. + camel.unregisterProducer(actorRef1) + (!processor.isStopped && !processor.isStopping) must be(true) + + camel.unregisterProducer(actorRef2) + (processor.isStopping || processor.isStopped) must be(true) + } + } + +} \ No newline at end of file diff --git a/akka-camel/src/test/scala/akka/camel/TestSupport.scala b/akka-camel/src/test/scala/akka/camel/TestSupport.scala new file mode 100644 index 0000000000..fee20267b1 --- /dev/null +++ b/akka-camel/src/test/scala/akka/camel/TestSupport.scala @@ -0,0 +1,90 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + +package akka.camel + +import akka.actor.{ Props, ActorSystem, Actor } +import akka.util.duration._ +import java.util.concurrent.{ TimeoutException, ExecutionException, TimeUnit } +import org.scalatest.{ BeforeAndAfterEach, BeforeAndAfterAll, Suite } +import org.scalatest.matchers.{ BePropertyMatcher, BePropertyMatchResult } +import akka.util.{ FiniteDuration, Duration } + +private[camel] object TestSupport { + + def start(actor: ⇒ Actor)(implicit system: ActorSystem) = { + val actorRef = system.actorOf(Props(actor)) + CamelExtension(system).awaitActivation(actorRef, 1 second) + actorRef + } + + private[camel] implicit def camelToTestWrapper(camel: Camel) = new CamelTestWrapper(camel) + + class CamelTestWrapper(camel: Camel) { + /** + * Sends msg to the endpoint and returns response. + * It only waits for the response until timeout passes. + * This is to reduce cases when unit-tests block infinitely. + */ + def sendTo(to: String, msg: String, timeout: Duration = 1 second): AnyRef = { + try { + camel.template.asyncRequestBody(to, msg).get(timeout.toNanos, TimeUnit.NANOSECONDS) + } catch { + case e: ExecutionException ⇒ throw e.getCause + case e: TimeoutException ⇒ throw new AssertionError("Failed to get response to message [%s], send to endpoint [%s], within [%s]" format (msg, to, timeout), e) + } + } + + def routeCount = camel.context.getRoutes().size() + } + + @deprecated + trait MessageSugar { + def Message(body: Any) = akka.camel.CamelMessage(body, Map.empty) + def Message(body: Any, headers: Map[String, Any]) = akka.camel.CamelMessage(body, headers) + } + + trait SharedCamelSystem extends BeforeAndAfterAll { this: Suite ⇒ + implicit lazy val system = ActorSystem("test") + implicit lazy val camel = CamelExtension(system) + + abstract override protected def afterAll() { + super.afterAll() + system.shutdown() + } + } + + trait NonSharedCamelSystem extends BeforeAndAfterEach { this: Suite ⇒ + implicit var system: ActorSystem = _ + implicit var camel: Camel = _ + + override protected def beforeEach() { + super.beforeEach() + system = ActorSystem("test") + camel = CamelExtension(system) + } + + override protected def afterEach() { + system.shutdown() + super.afterEach() + } + + } + def time[A](block: ⇒ A): FiniteDuration = { + val start = System.currentTimeMillis() + block + val duration = System.currentTimeMillis() - start + duration millis + } + + def anInstanceOf[T](implicit manifest: Manifest[T]) = { + val clazz = manifest.erasure.asInstanceOf[Class[T]] + new BePropertyMatcher[AnyRef] { + def apply(left: AnyRef) = BePropertyMatchResult( + clazz.isAssignableFrom(left.getClass), + "an instance of " + clazz.getName) + } + } + +} diff --git a/akka-camel/src/test/scala/akka/camel/UntypedProducerFeatureTest.scala b/akka-camel/src/test/scala/akka/camel/UntypedProducerFeatureTest.scala deleted file mode 100644 index 6039ddcc89..0000000000 --- a/akka-camel/src/test/scala/akka/camel/UntypedProducerFeatureTest.scala +++ /dev/null @@ -1,94 +0,0 @@ -package akka.camel - -import org.apache.camel.{ Exchange, Processor } -import org.apache.camel.builder.RouteBuilder -import org.apache.camel.component.mock.MockEndpoint -import org.scalatest.{ GivenWhenThen, BeforeAndAfterEach, BeforeAndAfterAll, FeatureSpec } - -import akka.actor.Actor._ - -class UntypedProducerFeatureTest extends FeatureSpec with BeforeAndAfterAll with BeforeAndAfterEach with GivenWhenThen { - import UntypedProducerFeatureTest._ - - override protected def beforeAll = { - registry.local.shutdownAll - CamelContextManager.init - CamelContextManager.mandatoryContext.addRoutes(new TestRoute) - CamelContextManager.start - } - - override protected def afterAll = { - CamelContextManager.stop - registry.local.shutdownAll - } - - override protected def afterEach = { - mockEndpoint.reset - } - - feature("Produce a message to a sync Camel route") { - - scenario("produce message and receive normal response") { - given("a registered two-way producer") - val producer = actorOf(classOf[SampleUntypedReplyingProducer]) - - when("a test message is sent to the producer with ?") - val message = Message("test", Map(Message.MessageExchangeId -> "123")) - val result = producer.ask(message).get - - then("a normal response should have been returned by the producer") - val expected = Message("received test", Map(Message.MessageExchangeId -> "123")) - assert(result === expected) - } - - scenario("produce message and receive failure response") { - given("a registered two-way producer") - val producer = actorOf(classOf[SampleUntypedReplyingProducer]) - - when("a test message causing an exception is sent to the producer with ?") - val message = Message("fail", Map(Message.MessageExchangeId -> "123")) - val result = producer.ask(message).as[Failure].get - - then("a failure response should have been returned by the producer") - val expectedFailureText = result.cause.getMessage - val expectedHeaders = result.headers - assert(expectedFailureText === "failure") - assert(expectedHeaders === Map(Message.MessageExchangeId -> "123")) - } - - } - - feature("Produce a message to a sync Camel route and then forward the response") { - - scenario("produce message and send normal response to direct:forward-test-1") { - given("a registered one-way producer configured with a forward target") - val producer = actorOf(classOf[SampleUntypedForwardingProducer]) - - when("a test message is sent to the producer with !") - mockEndpoint.expectedBodiesReceived("received test") - val result = producer.tell(Message("test"), producer) - - then("a normal response should have been sent") - mockEndpoint.assertIsSatisfied - } - - } - - private def mockEndpoint = CamelContextManager.mandatoryContext.getEndpoint("mock:mock", classOf[MockEndpoint]) -} - -object UntypedProducerFeatureTest { - class TestRoute extends RouteBuilder { - def configure { - from("direct:forward-test-1").to("mock:mock") - from("direct:producer-test-1").process(new Processor() { - def process(exchange: Exchange) = { - exchange.getIn.getBody match { - case "fail" ⇒ throw new Exception("failure") - case body ⇒ exchange.getOut.setBody("received %s" format body) - } - } - }) - } - } -} diff --git a/akka-camel/src/test/scala/akka/camel/UntypedProducerTest.scala b/akka-camel/src/test/scala/akka/camel/UntypedProducerTest.scala new file mode 100644 index 0000000000..460ac166cf --- /dev/null +++ b/akka-camel/src/test/scala/akka/camel/UntypedProducerTest.scala @@ -0,0 +1,101 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + +package akka.camel + +import org.apache.camel.{ Exchange, Processor } +import org.apache.camel.builder.RouteBuilder +import org.apache.camel.component.mock.MockEndpoint + +import akka.camel.TestSupport.SharedCamelSystem +import akka.actor.Props +import akka.pattern._ +import akka.dispatch.Await +import akka.util.duration._ +import org.scalatest._ + +class UntypedProducerTest extends WordSpec with BeforeAndAfterAll with BeforeAndAfterEach with SharedCamelSystem with GivenWhenThen { + import UntypedProducerTest._ + val timeout = 1 second + override protected def beforeAll = { + camel.context.addRoutes(new TestRoute) + } + + override protected def afterEach = { + mockEndpoint.reset + } + + "An UntypedProducer producing a message to a sync Camel route" must { + + "produce a message and receive a normal response" in { + given("a registered two-way producer") + val producer = system.actorOf(Props[SampleUntypedReplyingProducer]) + + when("a test message is sent to the producer with !!") + val message = CamelMessage("test", Map(CamelMessage.MessageExchangeId -> "123")) + val future = producer.ask(message)(timeout) + then("a normal response should have been returned by the producer") + val expected = CamelMessage("received test", Map(CamelMessage.MessageExchangeId -> "123")) + Await.result(future, timeout) match { + case result: CamelMessage ⇒ assert(result === expected) + case unexpected ⇒ fail("Actor responded with unexpected message:" + unexpected) + } + + } + + "produce a message and receive a failure response" in { + given("a registered two-way producer") + val producer = system.actorOf(Props[SampleUntypedReplyingProducer]) + + when("a test message causing an exception is sent to the producer with !!") + val message = CamelMessage("fail", Map(CamelMessage.MessageExchangeId -> "123")) + val future = producer.ask(message)(timeout) + then("a failure response should have been returned by the producer") + Await.result(future, timeout) match { + case result: Failure ⇒ { + val expectedFailureText = result.cause.getMessage + val expectedHeaders = result.headers + assert(expectedFailureText === "failure") + assert(expectedHeaders === Map(CamelMessage.MessageExchangeId -> "123")) + } + case unexpected ⇒ fail("Actor responded with unexpected message:" + unexpected) + } + } + + } + + "An UntypedProducer producing a message to a sync Camel route and then forwarding the response" must { + + "produce a message and send a normal response to direct:forward-test-1" in { + given("a registered one-way producer configured with a forward target") + val producer = system.actorOf(Props[SampleUntypedForwardingProducer]) + + when("a test message is sent to the producer with !") + mockEndpoint.expectedBodiesReceived("received test") + val result = producer.tell(CamelMessage("test", Map[String, Any]()), producer) + + then("a normal response should have been sent") + mockEndpoint.assertIsSatisfied + } + + } + + private def mockEndpoint = camel.context.getEndpoint("mock:mock", classOf[MockEndpoint]) +} + +object UntypedProducerTest { + class TestRoute extends RouteBuilder { + def configure { + from("direct:forward-test-1").to("mock:mock") + from("direct:producer-test-1").process(new Processor() { + def process(exchange: Exchange) = { + exchange.getIn.getBody match { + case "fail" ⇒ throw new Exception("failure") + case body ⇒ exchange.getOut.setBody("received %s" format body) + } + } + }) + } + } +} diff --git a/akka-camel/src/test/scala/akka/camel/component/ActorComponentFeatureTest.scala b/akka-camel/src/test/scala/akka/camel/component/ActorComponentFeatureTest.scala deleted file mode 100644 index a86577e92b..0000000000 --- a/akka-camel/src/test/scala/akka/camel/component/ActorComponentFeatureTest.scala +++ /dev/null @@ -1,129 +0,0 @@ -package akka.camel.component - -import java.util.concurrent.{ TimeUnit, CountDownLatch } - -import org.apache.camel.RuntimeCamelException -import org.apache.camel.builder.RouteBuilder -import org.apache.camel.component.mock.MockEndpoint -import org.scalatest.{ BeforeAndAfterEach, BeforeAndAfterAll, FeatureSpec } - -import akka.actor.{ Actor, Props, Timeout } -import akka.actor.Actor._ -import akka.camel.{ Failure, Message, CamelContextManager } -import akka.camel.CamelTestSupport._ - -class ActorComponentFeatureTest extends FeatureSpec with BeforeAndAfterAll with BeforeAndAfterEach { - import ActorComponentFeatureTest._ - - override protected def beforeAll = { - Actor.registry.local.shutdownAll - CamelContextManager.init - CamelContextManager.mandatoryContext.addRoutes(new TestRoute) - CamelContextManager.start - } - - override protected def afterAll = CamelContextManager.stop - - override protected def afterEach = { - Actor.registry.local.shutdownAll - mockEndpoint.reset - } - - feature("Communicate with an actor via an actor:uuid endpoint") { - import CamelContextManager.mandatoryTemplate - - scenario("one-way communication") { - val actor = actorOf(Props[Tester1] - val latch = (actor ? SetExpectedMessageCount(1)).as[CountDownLatch].get - mandatoryTemplate.sendBody("actor:uuid:%s" format actor.uuid, "Martin") - assert(latch.await(5000, TimeUnit.MILLISECONDS)) - val reply = (actor ? GetRetainedMessage).get.asInstanceOf[Message] - assert(reply.body === "Martin") - } - - scenario("two-way communication") { - val actor = actorOf(Props[Tester2] - assert(mandatoryTemplate.requestBody("actor:uuid:%s" format actor.uuid, "Martin") === "Hello Martin") - } - - scenario("two-way communication with timeout") { - val actor = actorOf(Props[Tester3].withTimeout(Timeout(1))) - intercept[RuntimeCamelException] { - mandatoryTemplate.requestBody("actor:uuid:%s?blocking=true" format actor.uuid, "Martin") - } - } - - scenario("two-way communication via a custom route with failure response") { - mockEndpoint.expectedBodiesReceived("whatever") - mandatoryTemplate.requestBody("direct:failure-test-1", "whatever") - mockEndpoint.assertIsSatisfied - } - - scenario("two-way communication via a custom route with exception") { - mockEndpoint.expectedBodiesReceived("whatever") - mandatoryTemplate.requestBody("direct:failure-test-2", "whatever") - mockEndpoint.assertIsSatisfied - } - } - - feature("Communicate with an actor via an actor:id endpoint") { - import CamelContextManager.mandatoryTemplate - - scenario("one-way communication") { - val actor = actorOf(Props[Tester1] - val latch = (actor ? SetExpectedMessageCount(1)).as[CountDownLatch].get - mandatoryTemplate.sendBody("actor:%s" format actor.address, "Martin") - assert(latch.await(5000, TimeUnit.MILLISECONDS)) - val reply = (actor ? GetRetainedMessage).get.asInstanceOf[Message] - assert(reply.body === "Martin") - } - - scenario("two-way communication") { - val actor = actorOf(Props[Tester2] - assert(mandatoryTemplate.requestBody("actor:%s" format actor.address, "Martin") === "Hello Martin") - } - - scenario("two-way communication via a custom route") { - val actor = actorOf(Props[CustomIdActor]("custom-id") - assert(mandatoryTemplate.requestBody("direct:custom-id-test-1", "Martin") === "Received Martin") - assert(mandatoryTemplate.requestBody("direct:custom-id-test-2", "Martin") === "Received Martin") - } - } - - private def mockEndpoint = CamelContextManager.mandatoryContext.getEndpoint("mock:mock", classOf[MockEndpoint]) -} - -object ActorComponentFeatureTest { - class CustomIdActor extends Actor { - protected def receive = { - case msg: Message ⇒ sender ! ("Received %s" format msg.body) - } - } - - class FailWithMessage extends Actor { - protected def receive = { - case msg: Message ⇒ sender ! Failure(new Exception("test")) - } - } - - class FailWithException extends Actor { - protected def receive = { - case msg: Message ⇒ throw new Exception("test") - } - } - - class TestRoute extends RouteBuilder { - val failWithMessage = actorOf(Props[FailWithMessage] - val failWithException = actorOf(Props[FailWithException] - def configure { - from("direct:custom-id-test-1").to("actor:custom-id") - from("direct:custom-id-test-2").to("actor:id:custom-id") - from("direct:failure-test-1") - .onException(classOf[Exception]).to("mock:mock").handled(true).end - .to("actor:uuid:%s" format failWithMessage.uuid) - from("direct:failure-test-2") - .onException(classOf[Exception]).to("mock:mock").handled(true).end - .to("actor:uuid:%s?blocking=true" format failWithException.uuid) - } - } -} diff --git a/akka-camel/src/test/scala/akka/camel/component/ActorComponentTest.scala b/akka-camel/src/test/scala/akka/camel/component/ActorComponentTest.scala deleted file mode 100644 index b84ba70105..0000000000 --- a/akka-camel/src/test/scala/akka/camel/component/ActorComponentTest.scala +++ /dev/null @@ -1,103 +0,0 @@ -package akka.camel.component - -import org.apache.camel.{ Endpoint, AsyncProcessor } -import org.apache.camel.impl.DefaultCamelContext -import org.junit._ -import org.scalatest.junit.JUnitSuite - -import akka.actor.uuidFrom - -class ActorComponentTest extends JUnitSuite { - val component: ActorComponent = ActorComponentTest.actorComponent - - def testUUID = "93da8c80-c3fd-11df-abed-60334b120057" - - @Test - def shouldCreateEndpointWithIdDefined = { - val ep1: ActorEndpoint = component.createEndpoint("actor:abc").asInstanceOf[ActorEndpoint] - val ep2: ActorEndpoint = component.createEndpoint("actor:id:abc").asInstanceOf[ActorEndpoint] - assert(ep1.idValue === Some("abc")) - assert(ep2.idValue === Some("abc")) - assert(ep1.idType === "id") - assert(ep2.idType === "id") - assert(!ep1.blocking) - assert(!ep2.blocking) - assert(ep1.autoack) - assert(ep2.autoack) - } - - @Test - def shouldCreateEndpointWithIdTemplate = { - val ep: ActorEndpoint = component.createEndpoint("actor:id:").asInstanceOf[ActorEndpoint] - assert(ep.idValue === None) - assert(ep.idType === "id") - assert(!ep.blocking) - assert(ep.autoack) - } - - @Test - def shouldCreateEndpointWithIdTemplateAndBlockingSet = { - val ep: ActorEndpoint = component.createEndpoint("actor:id:?blocking=true").asInstanceOf[ActorEndpoint] - assert(ep.idValue === None) - assert(ep.idType === "id") - assert(ep.blocking) - assert(ep.autoack) - } - - @Test - def shouldCreateEndpointWithUuidDefined = { - val ep: ActorEndpoint = component.createEndpoint("actor:uuid:%s" format testUUID).asInstanceOf[ActorEndpoint] - assert(ep.idValue === Some(testUUID)) - assert(ep.idType === "uuid") - assert(!ep.blocking) - assert(ep.autoack) - } - - @Test - def shouldCreateEndpointWithUuidTemplate = { - val ep: ActorEndpoint = component.createEndpoint("actor:uuid:").asInstanceOf[ActorEndpoint] - assert(ep.idValue === None) - assert(ep.idType === "uuid") - assert(!ep.blocking) - assert(ep.autoack) - } - - @Test - def shouldCreateEndpointWithUuidTemplateAndBlockingSet = { - val ep: ActorEndpoint = component.createEndpoint("actor:uuid:?blocking=true").asInstanceOf[ActorEndpoint] - assert(ep.idValue === None) - assert(ep.idType === "uuid") - assert(ep.blocking) - assert(ep.autoack) - } - - @Test - def shouldCreateEndpointWithBlockingSet = { - val ep: ActorEndpoint = component.createEndpoint("actor:uuid:%s?blocking=true" format testUUID).asInstanceOf[ActorEndpoint] - assert(ep.idValue === Some(testUUID)) - assert(ep.idType === "uuid") - assert(ep.blocking) - assert(ep.autoack) - } - - @Test - def shouldCreateEndpointWithAutoackUnset = { - val ep: ActorEndpoint = component.createEndpoint("actor:uuid:%s?autoack=false" format testUUID).asInstanceOf[ActorEndpoint] - assert(ep.idValue === Some(testUUID)) - assert(ep.idType === "uuid") - assert(!ep.blocking) - assert(!ep.autoack) - } -} - -object ActorComponentTest { - def actorComponent = { - val component = new ActorComponent - component.setCamelContext(new DefaultCamelContext) - component - } - - def actorEndpoint(uri: String) = actorComponent.createEndpoint(uri) - def actorProducer(endpoint: Endpoint) = endpoint.createProducer - def actorAsyncProducer(endpoint: Endpoint) = endpoint.createProducer.asInstanceOf[AsyncProcessor] -} diff --git a/akka-camel/src/test/scala/akka/camel/component/ActorProducerTest.scala b/akka-camel/src/test/scala/akka/camel/component/ActorProducerTest.scala deleted file mode 100644 index 31cbd33b0e..0000000000 --- a/akka-camel/src/test/scala/akka/camel/component/ActorProducerTest.scala +++ /dev/null @@ -1,254 +0,0 @@ -package akka.camel.component - -import ActorComponentTest._ - -import java.util.concurrent.{ CountDownLatch, TimeoutException, TimeUnit } - -import org.apache.camel.{ AsyncCallback, ExchangePattern } - -import org.junit.{ After, Test } -import org.scalatest.junit.JUnitSuite -import org.scalatest.BeforeAndAfterAll - -import akka.actor.{ Props, Timeout } -import akka.actor.Actor._ -import akka.camel.{ Failure, Message } -import akka.camel.CamelTestSupport._ - -class ActorProducerTest extends JUnitSuite with BeforeAndAfterAll { - import ActorProducerTest._ - - @After - def tearDown = registry.local.shutdownAll - - @Test - def shouldSendMessageToActorWithSyncProcessor = { - val actor = actorOf(Props[Tester1] - val latch = (actor ? SetExpectedMessageCount(1)).as[CountDownLatch].get - val endpoint = actorEndpoint("actor:uuid:%s" format actor.uuid) - val exchange = endpoint.createExchange(ExchangePattern.InOnly) - exchange.getIn.setBody("Martin") - exchange.getIn.setHeader("k1", "v1") - actorProducer(endpoint).process(exchange) - assert(latch.await(5000, TimeUnit.MILLISECONDS)) - val reply = (actor ? GetRetainedMessage).get.asInstanceOf[Message] - assert(reply.body === "Martin") - assert(reply.headers === Map(Message.MessageExchangeId -> exchange.getExchangeId, "k1" -> "v1")) - } - - @Test - def shouldSendMessageToActorWithAsyncProcessor = { - val actor = actorOf(Props[Tester1] - val latch = (actor ? SetExpectedMessageCount(1)).as[CountDownLatch].get - val endpoint = actorEndpoint("actor:uuid:%s" format actor.uuid) - val exchange = endpoint.createExchange(ExchangePattern.InOnly) - exchange.getIn.setBody("Martin") - exchange.getIn.setHeader("k1", "v1") - actorAsyncProducer(endpoint).process(exchange, expectSyncCompletion) - assert(latch.await(5000, TimeUnit.MILLISECONDS)) - val reply = (actor ? GetRetainedMessage).get.asInstanceOf[Message] - assert(reply.body === "Martin") - assert(reply.headers === Map(Message.MessageExchangeId -> exchange.getExchangeId, "k1" -> "v1")) - } - - @Test - def shouldSendMessageToActorAndReceiveResponseWithSyncProcessor = { - val actor = actorOf(Props(new Tester2 { - override def response(msg: Message) = Message(super.response(msg), Map("k2" -> "v2")) - }) - val endpoint = actorEndpoint("actor:uuid:%s" format actor.uuid) - val exchange = endpoint.createExchange(ExchangePattern.InOut) - exchange.getIn.setBody("Martin") - exchange.getIn.setHeader("k1", "v1") - actorProducer(endpoint).process(exchange) - assert(exchange.getOut.getBody === "Hello Martin") - assert(exchange.getOut.getHeader("k2") === "v2") - } - - @Test - def shouldSendMessageToActorAndReceiveResponseWithAsyncProcessor = { - val actor = actorOf(Props(new Tester2 { - override def response(msg: Message) = Message(super.response(msg), Map("k2" -> "v2")) - }) - val completion = expectAsyncCompletion - val endpoint = actorEndpoint("actor:uuid:%s" format actor.uuid) - val exchange = endpoint.createExchange(ExchangePattern.InOut) - exchange.getIn.setBody("Martin") - exchange.getIn.setHeader("k1", "v1") - actorAsyncProducer(endpoint).process(exchange, completion) - assert(completion.latch.await(5000, TimeUnit.MILLISECONDS)) - assert(exchange.getOut.getBody === "Hello Martin") - assert(exchange.getOut.getHeader("k2") === "v2") - } - - @Test - def shouldSendMessageToActorAndReceiveFailureWithAsyncProcessor = { - val actor = actorOf(Props(new Tester2 { - override def response(msg: Message) = Failure(new Exception("testmsg"), Map("k3" -> "v3")) - }) - val completion = expectAsyncCompletion - val endpoint = actorEndpoint("actor:uuid:%s" format actor.uuid) - val exchange = endpoint.createExchange(ExchangePattern.InOut) - exchange.getIn.setBody("Martin") - exchange.getIn.setHeader("k1", "v1") - actorAsyncProducer(endpoint).process(exchange, completion) - assert(completion.latch.await(5000, TimeUnit.MILLISECONDS)) - assert(exchange.getException.getMessage === "testmsg") - assert(exchange.getOut.getBody === null) - assert(exchange.getOut.getHeader("k3") === null) // headers from failure message are currently ignored - } - - @Test - def shouldSendMessageToActorAndReceiveAckWithAsyncProcessor = { - val actor = actorOf(Props(new Tester2 { - override def response(msg: Message) = akka.camel.Ack - }) - val completion = expectAsyncCompletion - val endpoint = actorEndpoint("actor:uuid:%s?autoack=false" format actor.uuid) - val exchange = endpoint.createExchange(ExchangePattern.InOnly) - exchange.getIn.setBody("Martin") - actorAsyncProducer(endpoint).process(exchange, completion) - assert(completion.latch.await(5000, TimeUnit.MILLISECONDS)) - assert(exchange.getIn.getBody === "Martin") - assert(exchange.getOut.getBody === null) - } - - @Test - def shouldDynamicallyRouteMessageToActorWithDefaultId = { - val actor1 = actorOf(Props[Tester1]("x") - val actor2 = actorOf(Props[Tester1]("y") - actor1 - actor2 - val latch1 = (actor1 ? SetExpectedMessageCount(1)).as[CountDownLatch].get - val latch2 = (actor2 ? SetExpectedMessageCount(1)).as[CountDownLatch].get - val endpoint = actorEndpoint("actor:id:%s" format actor1.address) - val exchange1 = endpoint.createExchange(ExchangePattern.InOnly) - val exchange2 = endpoint.createExchange(ExchangePattern.InOnly) - exchange1.getIn.setBody("Test1") - exchange2.getIn.setBody("Test2") - exchange2.getIn.setHeader(ActorComponent.ActorIdentifier, actor2.address) - actorProducer(endpoint).process(exchange1) - actorProducer(endpoint).process(exchange2) - assert(latch1.await(5, TimeUnit.SECONDS)) - assert(latch2.await(5, TimeUnit.SECONDS)) - val reply1 = (actor1 ? GetRetainedMessage).get.asInstanceOf[Message] - val reply2 = (actor2 ? GetRetainedMessage).get.asInstanceOf[Message] - assert(reply1.body === "Test1") - assert(reply2.body === "Test2") - } - - @Test - def shouldDynamicallyRouteMessageToActorWithoutDefaultId = { - val actor1 = actorOf(Props[Tester1]("x") - val actor2 = actorOf(Props[Tester1]("y") - actor1 - actor2 - val latch1 = (actor1 ? SetExpectedMessageCount(1)).as[CountDownLatch].get - val latch2 = (actor2 ? SetExpectedMessageCount(1)).as[CountDownLatch].get - val endpoint = actorEndpoint("actor:id:") - val exchange1 = endpoint.createExchange(ExchangePattern.InOnly) - val exchange2 = endpoint.createExchange(ExchangePattern.InOnly) - exchange1.getIn.setBody("Test1") - exchange2.getIn.setBody("Test2") - exchange1.getIn.setHeader(ActorComponent.ActorIdentifier, actor1.address) - exchange2.getIn.setHeader(ActorComponent.ActorIdentifier, actor2.address) - actorProducer(endpoint).process(exchange1) - actorProducer(endpoint).process(exchange2) - assert(latch1.await(5, TimeUnit.SECONDS)) - assert(latch2.await(5, TimeUnit.SECONDS)) - val reply1 = (actor1 ? GetRetainedMessage).get.asInstanceOf[Message] - val reply2 = (actor2 ? GetRetainedMessage).get.asInstanceOf[Message] - assert(reply1.body === "Test1") - assert(reply2.body === "Test2") - } - - @Test - def shouldDynamicallyRouteMessageToActorWithDefaultUuid = { - val actor1 = actorOf(Props[Tester1] - val actor2 = actorOf(Props[Tester1] - val latch1 = (actor1 ? SetExpectedMessageCount(1)).as[CountDownLatch].get - val latch2 = (actor2 ? SetExpectedMessageCount(1)).as[CountDownLatch].get - val endpoint = actorEndpoint("actor:uuid:%s" format actor1.uuid) - val exchange1 = endpoint.createExchange(ExchangePattern.InOnly) - val exchange2 = endpoint.createExchange(ExchangePattern.InOnly) - exchange1.getIn.setBody("Test1") - exchange2.getIn.setBody("Test2") - exchange2.getIn.setHeader(ActorComponent.ActorIdentifier, actor2.uuid.toString) - actorProducer(endpoint).process(exchange1) - actorProducer(endpoint).process(exchange2) - assert(latch1.await(5, TimeUnit.SECONDS)) - assert(latch2.await(5, TimeUnit.SECONDS)) - val reply1 = (actor1 ? GetRetainedMessage).get.asInstanceOf[Message] - val reply2 = (actor2 ? GetRetainedMessage).get.asInstanceOf[Message] - assert(reply1.body === "Test1") - assert(reply2.body === "Test2") - } - - @Test - def shouldDynamicallyRouteMessageToActorWithoutDefaultUuid = { - val actor1 = actorOf(Props[Tester1] - val actor2 = actorOf(Props[Tester1] - val latch1 = (actor1 ? SetExpectedMessageCount(1)).as[CountDownLatch].get - val latch2 = (actor2 ? SetExpectedMessageCount(1)).as[CountDownLatch].get - val endpoint = actorEndpoint("actor:uuid:") - val exchange1 = endpoint.createExchange(ExchangePattern.InOnly) - val exchange2 = endpoint.createExchange(ExchangePattern.InOnly) - exchange1.getIn.setBody("Test1") - exchange2.getIn.setBody("Test2") - exchange1.getIn.setHeader(ActorComponent.ActorIdentifier, actor1.uuid) - exchange2.getIn.setHeader(ActorComponent.ActorIdentifier, actor2.uuid.toString) - actorProducer(endpoint).process(exchange1) - actorProducer(endpoint).process(exchange2) - assert(latch1.await(5, TimeUnit.SECONDS)) - assert(latch2.await(5, TimeUnit.SECONDS)) - val reply1 = (actor1 ? GetRetainedMessage).get.asInstanceOf[Message] - val reply2 = (actor2 ? GetRetainedMessage).get.asInstanceOf[Message] - assert(reply1.body === "Test1") - assert(reply2.body === "Test2") - } - - @Test - def shouldThrowExceptionWhenIdNotSet{ - val actor = actorOf(Props[Tester1] - val latch = (actor ? SetExpectedMessageCount(1)).as[CountDownLatch].get - val endpoint = actorEndpoint("actor:id:") - intercept[ActorIdentifierNotSetException] { - actorProducer(endpoint).process(endpoint.createExchange(ExchangePattern.InOnly)) - } - } - - @Test - def shouldThrowExceptionWhenUuidNotSet{ - val actor = actorOf(Props[Tester1] - val latch = (actor ? SetExpectedMessageCount(1)).as[CountDownLatch].get - val endpoint = actorEndpoint("actor:uuid:") - intercept[ActorIdentifierNotSetException] { - actorProducer(endpoint).process(endpoint.createExchange(ExchangePattern.InOnly)) - } - } - - @Test - def shouldSendMessageToActorAndTimeout() { - val actor = actorOf(Props[Tester3].withTimeout(Timeout(1))) - val endpoint = actorEndpoint("actor:uuid:%s" format actor.uuid) - val exchange = endpoint.createExchange(ExchangePattern.InOut) - exchange.getIn.setBody("Martin") - intercept[TimeoutException] { - endpoint.createProducer.process(exchange) - } - } -} - -object ActorProducerTest { - def expectSyncCompletion = new AsyncCallback { - def done(doneSync: Boolean) = assert(doneSync) - } - - def expectAsyncCompletion = new AsyncCallback { - val latch = new CountDownLatch(1); - def done(doneSync: Boolean) = { - assert(!doneSync) - latch.countDown - } - } -} diff --git a/akka-camel/src/test/scala/akka/camel/internal/ActivationTrackerTest.scala b/akka-camel/src/test/scala/akka/camel/internal/ActivationTrackerTest.scala new file mode 100644 index 0000000000..d5b6792975 --- /dev/null +++ b/akka-camel/src/test/scala/akka/camel/internal/ActivationTrackerTest.scala @@ -0,0 +1,136 @@ +package akka.camel.internal + +import org.scalatest.matchers.MustMatchers +import akka.testkit.{ TestProbe, TestKit } +import akka.util.duration._ +import org.scalatest.{ GivenWhenThen, BeforeAndAfterEach, BeforeAndAfterAll, WordSpec } +import akka.actor.{ Props, ActorSystem } +import akka.util.Duration + +class ActivationTrackerTest extends TestKit(ActorSystem("test")) with WordSpec with MustMatchers with BeforeAndAfterAll with BeforeAndAfterEach with GivenWhenThen { + + override protected def afterAll() { system.shutdown() } + + var actor: TestProbe = _ + var awaiting: Awaiting = _ + var anotherAwaiting: Awaiting = _ + val cause = new Exception("cause of failure") + + override protected def beforeEach() { + actor = TestProbe() + awaiting = new Awaiting(actor) + anotherAwaiting = new Awaiting(actor) + } + + val at = system.actorOf(Props[ActivationTracker]) + + "ActivationTracker forwards activation message to all awaiting parties" in { + awaiting.awaitActivation() + anotherAwaiting.awaitActivation() + + publish(EndpointActivated(actor.ref)) + + awaiting.verifyActivated() + anotherAwaiting.verifyActivated() + } + + "ActivationTracker send activation message even if activation happened earlier" in { + publish(EndpointActivated(actor.ref)) + Thread.sleep(50) + awaiting.awaitActivation() + + awaiting.verifyActivated() + } + + "ActivationTracker send activation message even if actor is already deactivated" in { + publish(EndpointActivated(actor.ref)) + publish(EndpointDeActivated(actor.ref)) + Thread.sleep(50) + awaiting.awaitActivation() + + awaiting.verifyActivated() + } + + "ActivationTracker forwards de-activation message to all awaiting parties" in { + given("Actor is activated") + publish(EndpointActivated(actor.ref)) + given("Actor is deactivated") + publish(EndpointDeActivated(actor.ref)) + + when("Multiple parties await deactivation") + awaiting.awaitDeActivation() + anotherAwaiting.awaitDeActivation() + + then("all awaiting parties are notified") + awaiting.verifyDeActivated() + anotherAwaiting.verifyDeActivated() + } + + "ActivationTracker forwards de-activation message even if deactivation happened earlier" in { + given("Actor is activated") + publish(EndpointActivated(actor.ref)) + + given("Someone is awaiting de-activation") + awaiting.awaitDeActivation() + + when("Actor is de-activated") + publish(EndpointDeActivated(actor.ref)) + + then("Awaiting gets notified") + awaiting.verifyDeActivated() + } + + "ActivationTracker forwards de-activation message even if someone awaits de-activation even before activation happens" in { + given("Someone is awaiting de-activation") + val awaiting = new Awaiting(actor) + awaiting.awaitDeActivation() + + given("Actor is activated") + publish(EndpointActivated(actor.ref)) + + when("Actor is de-activated") + publish(EndpointDeActivated(actor.ref)) + + then("Awaiting gets notified") + awaiting.verifyDeActivated() + } + + "ActivationTracker sends activation failure when failed to activate" in { + awaiting.awaitActivation() + publish(EndpointFailedToActivate(actor.ref, cause)) + + awaiting.verifyFailedToActivate() + } + + "ActivationTracker sends de-activation failure when failed to de-activate" in { + publish(EndpointActivated(actor.ref)) + awaiting.awaitDeActivation() + publish(EndpointFailedToDeActivate(actor.ref, cause)) + + awaiting.verifyFailedToDeActivate() + } + + "ActivationTracker sends activation message even if it failed to de-activate" in { + publish(EndpointActivated(actor.ref)) + publish(EndpointFailedToDeActivate(actor.ref, cause)) + awaiting.awaitActivation() + + awaiting.verifyActivated() + } + + def publish(msg: AnyRef) = system.eventStream.publish(msg) + + class Awaiting(actor: TestProbe) { + val probe = TestProbe() + def awaitActivation() = at.tell(AwaitActivation(actor.ref), probe.ref) + def awaitDeActivation() = at.tell(AwaitDeActivation(actor.ref), probe.ref) + def verifyActivated(timeout: Duration = 50 millis) = within(timeout) { probe.expectMsg(EndpointActivated(actor.ref)) } + def verifyDeActivated(timeout: Duration = 50 millis) = within(timeout) { probe.expectMsg(EndpointDeActivated(actor.ref)) } + + def verifyFailedToActivate(timeout: Duration = 50 millis) = within(timeout) { probe.expectMsg(EndpointFailedToActivate(actor.ref, cause)) } + def verifyFailedToDeActivate(timeout: Duration = 50 millis) = within(timeout) { probe.expectMsg(EndpointFailedToDeActivate(actor.ref, cause)) } + + } + +} + diff --git a/akka-camel/src/test/scala/akka/camel/internal/component/ActorComponentConfigurationTest.scala b/akka-camel/src/test/scala/akka/camel/internal/component/ActorComponentConfigurationTest.scala new file mode 100644 index 0000000000..dd37716295 --- /dev/null +++ b/akka-camel/src/test/scala/akka/camel/internal/component/ActorComponentConfigurationTest.scala @@ -0,0 +1,27 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + +package akka.camel.internal.component + +import org.scalatest.matchers.MustMatchers +import akka.util.duration._ +import akka.camel.TestSupport.SharedCamelSystem +import org.apache.camel.Component +import org.scalatest.WordSpec + +class ActorComponentConfigurationTest extends WordSpec with MustMatchers with SharedCamelSystem { + + val component: Component = camel.context.getComponent("actor") + + "Endpoint url config must be correctly parsed" in { + val actorEndpointConfig = component.createEndpoint("actor://path:akka://test/user/$a?autoack=false&replyTimeout=987000000+nanos").asInstanceOf[ActorEndpointConfig] + + actorEndpointConfig must have( + 'endpointUri("actor://path:akka://test/user/$a?autoack=false&replyTimeout=987000000+nanos"), + 'path(ActorEndpointPath.fromCamelPath("path:akka://test/user/$a")), + 'autoack(false), + 'replyTimeout(987000000 nanos)) + } + +} \ No newline at end of file diff --git a/akka-camel/src/test/scala/akka/camel/internal/component/ActorEndpointPathTest.scala b/akka-camel/src/test/scala/akka/camel/internal/component/ActorEndpointPathTest.scala new file mode 100644 index 0000000000..eddec13b8b --- /dev/null +++ b/akka-camel/src/test/scala/akka/camel/internal/component/ActorEndpointPathTest.scala @@ -0,0 +1,27 @@ +/** + * Copyright (C) 2009when2012 Typesafe Inc. + */ + +package akka.camel.internal.component + +import org.scalatest.mock.MockitoSugar +import org.scalatest.matchers.MustMatchers +import akka.camel.TestSupport.SharedCamelSystem +import akka.actor.Props +import org.scalatest.WordSpec + +class ActorEndpointPathTest extends WordSpec with SharedCamelSystem with MustMatchers with MockitoSugar { + + def find(path: String) = ActorEndpointPath.fromCamelPath("path:" + path).findActorIn(system) + + "findActorIn returns Some(actor ref) if actor exists" in { + val path = system.actorOf(Props(behavior = ctx ⇒ { case _ ⇒ {} })).path + find(path.toString) must be('defined) + } + + "findActorIn returns None" when { + "invalid path" in { find("some_invalid_path") must be(None) } + "non existing valid path" in { find("akka://system/user/$a") must be(None) } + } + +} \ No newline at end of file diff --git a/akka-camel/src/test/scala/akka/camel/internal/component/ActorProducerTest.scala b/akka-camel/src/test/scala/akka/camel/internal/component/ActorProducerTest.scala new file mode 100644 index 0000000000..6199aaa7c6 --- /dev/null +++ b/akka-camel/src/test/scala/akka/camel/internal/component/ActorProducerTest.scala @@ -0,0 +1,340 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + +package akka.camel.internal.component + +import org.scalatest.mock.MockitoSugar +import org.mockito.Matchers.{ eq ⇒ the, any } +import org.mockito.Mockito._ +import org.apache.camel.AsyncCallback +import java.util.concurrent.atomic.AtomicBoolean +import akka.util.duration._ +import akka.util.Duration +import akka.testkit.{ TestKit, TestProbe } +import java.lang.String +import akka.actor.{ ActorRef, Props, ActorSystem, Actor } +import akka.camel._ +import internal.CamelExchangeAdapter +import org.scalatest.{ Suite, WordSpec, BeforeAndAfterAll, BeforeAndAfterEach } +import akka.camel.TestSupport._ +import java.util.concurrent.{ TimeoutException, CountDownLatch, TimeUnit } +import org.mockito.{ ArgumentMatcher, Matchers, Mockito } +import org.scalatest.matchers.MustMatchers + +class ActorProducerTest extends TestKit(ActorSystem("test")) with WordSpec with MustMatchers with ActorProducerFixture { + + "ActorProducer" when { + + "synchronous" when { + + "consumer actor doesnt exist" must { + "set failure message on exchange" in { + producer = given(actor = null) + producer.processExchangeAdapter(exchange) + + verify(exchange).setFailure(any[Failure]) + } + } + + "in-only" must { + def producer = given(outCapable = false) + + "pass the message to the consumer" in { + producer.processExchangeAdapter(exchange) + within(1 second)(probe.expectMsg(message)) + } + + "not expect response and not block" in { + time(producer.processExchangeAdapter(exchange)) must be < (30 millis) + } + + } + + "out capable" when { + "response is sent back by actor" must { + + "get a response" in { + producer = given(actor = echoActor, outCapable = true) + + producer.processExchangeAdapter(exchange) + + verify(exchange).setResponse(msg("received " + message)) + } + } + + "response is not sent by actor" must { + + def process() = { + producer = given(outCapable = true, replyTimeout = 100 millis) + time(producer.processExchangeAdapter(exchange)) + } + + "timeout after replyTimeout" in { + val duration = process() + duration must (be >= (100 millis) and be < (300 millis)) + } + + "never set the response on exchange" in { + process() + verify(exchange, Mockito.never()).setResponse(any[CamelMessage]) + } + + "set failure message to timeout" in { + process() + verify(exchange).setFailure(any[Failure]) + } + } + + } + + //TODO: write more tests for synchronous process(exchange) method + + } + + "asynchronous" when { + + def verifyFailureIsSet { + producer.processExchangeAdapter(exchange, asyncCallback) + asyncCallback.awaitCalled() + verify(exchange).setFailure(any[Failure]) + } + + "out-capable" when { + + "consumer actor doesnt exist" must { + "set failure message on exchange" in { + producer = given(actor = null, outCapable = true) + verifyFailureIsSet + } + } + + "response is ok" must { + "get a response and async callback as soon as it gets the response (but not before)" in { + producer = given(outCapable = true) + + val doneSync = producer.processExchangeAdapter(exchange, asyncCallback) + + asyncCallback.expectNoCallWithin(100 millis); info("no async callback before response") + + within(1 second) { + probe.expectMsgType[CamelMessage] + probe.sender ! "some message" + } + doneSync must be(false); info("done async") + + asyncCallback.expectDoneAsyncWithin(1 second); info("async callback received") + verify(exchange).setResponse(msg("some message")); info("response as expected") + } + } + + "response is Failure" must { + "set an exception on exchange" in { + val failure = Failure(new RuntimeException("some failure")) + + producer = given(outCapable = true) + + producer.processExchangeAdapter(exchange, asyncCallback) + + within(1 second) { + probe.expectMsgType[CamelMessage] + probe.sender ! failure + asyncCallback.awaitCalled(remaining) + } + + verify(exchange).setFailure(failure) + } + } + + "no response is sent within timeout" must { + "set TimeoutException on exchange" in { + producer = given(outCapable = true, replyTimeout = 10 millis) + producer.processExchangeAdapter(exchange, asyncCallback) + asyncCallback.awaitCalled(100 millis) + verify(exchange).setFailure(Matchers.argThat(new ArgumentMatcher[Failure] { + def matches(failure: AnyRef) = { failure.asInstanceOf[Failure].getCause must be(anInstanceOf[TimeoutException]); true } + + })) + } + } + + } + + "in-only" when { + + "consumer actor doesnt exist" must { + "set failure message on exchange" in { + producer = given(actor = null, outCapable = false) + verifyFailureIsSet + } + } + + "autoAck" must { + + "get sync callback as soon as it sends a message" in { + + producer = given(outCapable = false, autoAck = true) + val doneSync = producer.processExchangeAdapter(exchange, asyncCallback) + + doneSync must be(true); info("done sync") + asyncCallback.expectDoneSyncWithin(1 second); info("async callback called") + verify(exchange, never()).setResponse(any[CamelMessage]); info("no response forwarded to exchange") + } + + } + + "manualAck" when { + + "response is Ack" must { + "get async callback" in { + producer = given(outCapable = false, autoAck = false) + + val doneSync = producer.processExchangeAdapter(exchange, asyncCallback) + + doneSync must be(false) + within(1 second) { + probe.expectMsgType[CamelMessage]; info("message sent to consumer") + probe.sender ! Ack + asyncCallback.expectDoneAsyncWithin(remaining); info("async callback called") + } + verify(exchange, never()).setResponse(any[CamelMessage]); info("no response forwarded to exchange") + } + } + + "expecting Ack or Failure message and some other message is sent as a response" must { + "fail" in { + producer = given(outCapable = false, autoAck = false) + + producer.processExchangeAdapter(exchange, asyncCallback) + + within(1 second) { + probe.expectMsgType[CamelMessage]; info("message sent to consumer") + probe.sender ! "some neither Ack nor Failure response" + asyncCallback.expectDoneAsyncWithin(remaining); info("async callback called") + } + verify(exchange, never()).setResponse(any[CamelMessage]); info("no response forwarded to exchange") + verify(exchange).setFailure(any[Failure]); info("failure set") + + } + } + + "no Ack is sent within timeout" must { + "set failure on exchange" in { + producer = given(outCapable = false, replyTimeout = 10 millis, autoAck = false) + + producer.processExchangeAdapter(exchange, asyncCallback) + asyncCallback.awaitCalled(100 millis) + verify(exchange).setFailure(any[Failure]) + + } + } + + "response is Failure" must { + "set an exception on exchange" in { + producer = given(outCapable = false, autoAck = false) + + val doneSync = producer.processExchangeAdapter(exchange, asyncCallback) + + doneSync must be(false) + within(1 second) { + probe.expectMsgType[CamelMessage]; info("message sent to consumer") + probe.sender ! Failure(new Exception) + asyncCallback.awaitCalled(remaining); + } + verify(exchange, never()).setResponse(any[CamelMessage]); info("no response forwarded to exchange") + verify(exchange).setFailure(any[Failure]); info("failure set") + } + } + } + } + } + } +} + +trait ActorProducerFixture extends MockitoSugar with BeforeAndAfterAll with BeforeAndAfterEach { self: TestKit with MustMatchers with Suite ⇒ + var camel: Camel = _ + var exchange: CamelExchangeAdapter = _ + var callback: AsyncCallback = _ + + var producer: ActorProducer = _ + var message: CamelMessage = _ + var probe: TestProbe = _ + var asyncCallback: TestAsyncCallback = _ + var actorEndpointPath: ActorEndpointPath = _ + var actorComponent: ActorComponent = _ + + override protected def beforeEach() { + asyncCallback = createAsyncCallback + + probe = TestProbe() + camel = mock[Camel] + exchange = mock[CamelExchangeAdapter] + callback = mock[AsyncCallback] + actorEndpointPath = mock[ActorEndpointPath] + actorComponent = mock[ActorComponent] + producer = new ActorProducer(config(), camel) + message = CamelMessage(null, null) + } + + override protected def afterAll() { + system.shutdown() + } + + def msg(s: String) = CamelMessage(s, Map.empty) + + def given(actor: ActorRef = probe.ref, outCapable: Boolean = true, autoAck: Boolean = true, replyTimeout: Duration = Int.MaxValue seconds) = { + prepareMocks(actor, outCapable = outCapable) + new ActorProducer(config(isAutoAck = autoAck, _replyTimeout = replyTimeout), camel) + } + + def createAsyncCallback = new TestAsyncCallback + + class TestAsyncCallback extends AsyncCallback { + def expectNoCallWithin(duration: Duration) { + if (callbackReceived.await(duration.toNanos, TimeUnit.NANOSECONDS)) fail("NOT expected callback, but received one!") + } + + def awaitCalled(timeout: Duration = 1 second) { valueWithin(1 second) } + + val callbackReceived = new CountDownLatch(1) + val callbackValue = new AtomicBoolean() + + def done(doneSync: Boolean) { + callbackValue set doneSync + callbackReceived.countDown() + } + + private[this] def valueWithin(implicit timeout: Duration) = { + if (!callbackReceived.await(timeout.toNanos, TimeUnit.NANOSECONDS)) fail("Callback not received!") + callbackValue.get + } + + def expectDoneSyncWithin(implicit timeout: Duration) { + if (!valueWithin(timeout)) fail("Expected to be done Synchronously") + } + def expectDoneAsyncWithin(implicit timeout: Duration) { + if (valueWithin(timeout)) fail("Expected to be done Asynchronously") + } + + } + + def config(endpointUri: String = "test-uri", isAutoAck: Boolean = true, _replyTimeout: Duration = Int.MaxValue seconds) = { + val endpoint = new ActorEndpoint(endpointUri, actorComponent, actorEndpointPath, camel) + endpoint.autoack = isAutoAck + endpoint.replyTimeout = _replyTimeout + endpoint + } + + def prepareMocks(actor: ActorRef, message: CamelMessage = message, outCapable: Boolean) { + when(actorEndpointPath.findActorIn(any[ActorSystem])) thenReturn Option(actor) + when(exchange.toRequestMessage(any[Map[String, Any]])) thenReturn message + when(exchange.isOutCapable) thenReturn outCapable + } + + def echoActor = system.actorOf(Props(new Actor { + protected def receive = { + case msg ⇒ sender ! "received " + msg + } + })) + +} diff --git a/akka-camel/src/test/scala/akka/camel/internal/component/DurationConverterTest.scala b/akka-camel/src/test/scala/akka/camel/internal/component/DurationConverterTest.scala new file mode 100644 index 0000000000..3787a9f46f --- /dev/null +++ b/akka-camel/src/test/scala/akka/camel/internal/component/DurationConverterTest.scala @@ -0,0 +1,36 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + +package akka.camel.internal.component + +import org.scalatest.matchers.MustMatchers +import akka.util.duration._ +import akka.util.Duration +import org.scalatest.WordSpec + +class DurationConverterTest extends WordSpec with MustMatchers { + import DurationTypeConverter._ + + "DurationTypeConverter must convert '10 nanos'" in { + convertTo(classOf[Duration], "10 nanos") must be(10 nanos) + } + + "DurationTypeConverter must do the roundtrip" in { + convertTo(classOf[Duration], DurationTypeConverter.toString(10 seconds)) must be(10 seconds) + } + + "DurationTypeConverter must throw if invalid format" in { + intercept[Exception] { + convertTo(classOf[Duration], "abc nanos") must be(10 nanos) + } + } + + "DurationTypeConverter must throw if doesn't end with nanos" in { + intercept[Exception] { + convertTo(classOf[Duration], "10233") must be(10 nanos) + } + } + +} + diff --git a/akka-camel/src/test/scala/application.conf b/akka-camel/src/test/scala/application.conf new file mode 100644 index 0000000000..2b75ae7a47 --- /dev/null +++ b/akka-camel/src/test/scala/application.conf @@ -0,0 +1,3 @@ +akka{ + loglevel = "ERROR" +} \ No newline at end of file diff --git a/akka-cluster/src/test/scala/akka/cluster/ClusterConfigSpec.scala b/akka-cluster/src/test/scala/akka/cluster/ClusterConfigSpec.scala index 3a92b46eec..6d57718455 100644 --- a/akka-cluster/src/test/scala/akka/cluster/ClusterConfigSpec.scala +++ b/akka-cluster/src/test/scala/akka/cluster/ClusterConfigSpec.scala @@ -16,7 +16,7 @@ class ClusterConfigSpec extends ClusterSpec { "be able to parse generic cluster config elements" in { val settings = new ClusterSettings(system.settings.config, system.name) import settings._ - FailureDetectorThreshold must be(8) + FailureDetectorThreshold must be(3) FailureDetectorMaxSampleSize must be(1000) NodeToJoin must be(None) PeriodicTasksInitialDelay must be(1 seconds) diff --git a/akka-cluster/src/test/scala/akka/cluster/NodeStartupSpec.scala b/akka-cluster/src/test/scala/akka/cluster/NodeStartupSpec.scala index 26bed27e65..6310bde607 100644 --- a/akka-cluster/src/test/scala/akka/cluster/NodeStartupSpec.scala +++ b/akka-cluster/src/test/scala/akka/cluster/NodeStartupSpec.scala @@ -29,12 +29,12 @@ class NodeStartupSpec extends ClusterSpec with ImplicitSender { val remote0 = system0.provider.asInstanceOf[RemoteActorRefProvider] node0 = Node(system0) - "be a singleton cluster when started up" in { + "be a singleton cluster when started up" taggedAs LongRunningTest in { Thread.sleep(1.seconds.dilated.toMillis) node0.isSingletonCluster must be(true) } - "be in 'Up' phase when started up" in { + "be in 'Joining' phase when started up" taggedAs LongRunningTest in { val members = node0.latestGossip.members val joiningMember = members find (_.address.port.get == 5550) joiningMember must be('defined) @@ -43,7 +43,7 @@ class NodeStartupSpec extends ClusterSpec with ImplicitSender { } "A second cluster node with a 'node-to-join' config defined" must { - "join the other node cluster as 'Joining' when sending a Join command" in { + "join the other node cluster when sending a Join command" taggedAs LongRunningTest in { system1 = ActorSystem("system1", ConfigFactory .parseString(""" akka { @@ -55,11 +55,11 @@ class NodeStartupSpec extends ClusterSpec with ImplicitSender { val remote1 = system1.provider.asInstanceOf[RemoteActorRefProvider] node1 = Node(system1) - Thread.sleep(5.seconds.dilated.toMillis) // give enough time for node1 to JOIN node0 + Thread.sleep(10.seconds.dilated.toMillis) // give enough time for node1 to JOIN node0 and leader to move him to UP val members = node0.latestGossip.members val joiningMember = members find (_.address.port.get == 5551) joiningMember must be('defined) - joiningMember.get.status must be(MemberStatus.Joining) + joiningMember.get.status must be(MemberStatus.Up) } } } catch { diff --git a/akka-docs/dev/developer-guidelines.rst b/akka-docs/dev/developer-guidelines.rst index bc52613d47..59f5a85349 100644 --- a/akka-docs/dev/developer-guidelines.rst +++ b/akka-docs/dev/developer-guidelines.rst @@ -13,9 +13,11 @@ Akka is using ``Scalariform`` to format the source code as part of the build. So Process ------- +* Make sure you have signed the Akka CLA, if not, ask for it on the Mailing List. * Pick a ticket, if there is no ticket for your work then create one first. -* Start working in a feature branch. Name it something like ``wip--``. -* When you are done send a request for review to the Akka developer mailing list. If successfully review, merge into master. +* Start working in a feature branch. Name it something like ``wip---``. +* When you are done, create a GitHub Pull-Request towards the targeted branch and email the Akka Mailing List that you want it reviewed +* When there's consensus on the review, someone from the Akka Core Team will merge it. Commit messages --------------- diff --git a/akka-docs/java/serialization.rst b/akka-docs/java/serialization.rst index 576b8c50ee..4c7b023959 100644 --- a/akka-docs/java/serialization.rst +++ b/akka-docs/java/serialization.rst @@ -109,6 +109,23 @@ you might want to know how to serialize and deserialize them properly, here's th .. includecode:: code/akka/docs/serialization/SerializationDocTestBase.java :include: imports,actorref-serializer +Deep serialization of Actors +---------------------------- + +The current recommended approach to do deep serialization of internal actor state is to use Event Sourcing, +for more reading on the topic, see these examples: + +`Martin Krasser on EventSourcing Part1 `_ + +`Martin Krasser on EventSourcing Part2 `_ + + +.. note:: + + Built-in API support for persisting Actors will come in a later release, see the roadmap for more info: + + `Akka 2.0 roadmap `_ + A Word About Java Serialization =============================== diff --git a/akka-docs/java/untyped-actors.rst b/akka-docs/java/untyped-actors.rst index b3a0eb3928..4ba40a8f1f 100644 --- a/akka-docs/java/untyped-actors.rst +++ b/akka-docs/java/untyped-actors.rst @@ -287,6 +287,13 @@ Messages are sent to an Actor through one of the following methods. Message ordering is guaranteed on a per-sender basis. +.. note:: + + There are performance implications of using ``ask`` since something needs to + keep track of when it times out, there needs to be something that bridges + a ``Promise`` into an ``ActorRef`` and it also needs to be reachable through + remoting. So always prefer ``tell`` for performance, and only ``ask`` if you must. + In all these methods you have the option of passing along your own ``ActorRef``. Make it a practice of doing so because it will allow the receiver actors to be able to respond to your message, since the sender reference is sent along with the message. diff --git a/akka-docs/project/scaladoc.rst b/akka-docs/project/scaladoc.rst index 4ad09caad1..b3aeed9fe3 100644 --- a/akka-docs/project/scaladoc.rst +++ b/akka-docs/project/scaladoc.rst @@ -12,7 +12,8 @@ Release Versions 2.0 ----- -- Akka 2.0 - http://doc.akka.io/api/akka/2.0/ +- Akka - http://doc.akka.io/api/akka/2.0/ + 1.3.1 ----- diff --git a/akka-docs/scala/actors.rst b/akka-docs/scala/actors.rst index 8440a46f3e..6c819facda 100644 --- a/akka-docs/scala/actors.rst +++ b/akka-docs/scala/actors.rst @@ -331,6 +331,13 @@ Messages are sent to an Actor through one of the following methods. Message ordering is guaranteed on a per-sender basis. +.. note:: + + There are performance implications of using ``ask`` since something needs to + keep track of when it times out, there needs to be something that bridges + a ``Promise`` into an ``ActorRef`` and it also needs to be reachable through + remoting. So always prefer ``tell`` for performance, and only ``ask`` if you must. + Tell: Fire-forget ----------------- diff --git a/akka-docs/scala/serialization.rst b/akka-docs/scala/serialization.rst index 4811805ebd..2ab0a7b633 100644 --- a/akka-docs/scala/serialization.rst +++ b/akka-docs/scala/serialization.rst @@ -107,6 +107,23 @@ you might want to know how to serialize and deserialize them properly, here's th .. includecode:: code/akka/docs/serialization/SerializationDocSpec.scala :include: imports,actorref-serializer +Deep serialization of Actors +---------------------------- + +The current recommended approach to do deep serialization of internal actor state is to use Event Sourcing, +for more reading on the topic, see these examples: + +`Martin Krasser on EventSourcing Part1 `_ + +`Martin Krasser on EventSourcing Part2 `_ + + +.. note:: + + Built-in API support for persisting Actors will come in a later release, see the roadmap for more info: + + `Akka 2.0 roadmap `_ + A Word About Java Serialization =============================== diff --git a/akka-remote/src/multi-jvm/scala/akka/remote/DirectRoutedRemoteActorMultiJvmSpec.scala b/akka-remote/src/multi-jvm/scala/akka/remote/DirectRoutedRemoteActorMultiJvmSpec.scala index 1c7d4b2602..3026ddd613 100644 --- a/akka-remote/src/multi-jvm/scala/akka/remote/DirectRoutedRemoteActorMultiJvmSpec.scala +++ b/akka-remote/src/multi-jvm/scala/akka/remote/DirectRoutedRemoteActorMultiJvmSpec.scala @@ -55,8 +55,10 @@ class DirectRoutedRemoteActorMultiJvmNode2 extends AkkaRemoteSpec(nodeConfigs(1) Await.result(actor ? "identify", timeout.duration).asInstanceOf[ActorRef].path.address.hostPort must equal(akkaSpec(0)) + // shut down the actor before we let the other node(s) shut down so we don't try to send + // "Terminate" to a shut down node + system.stop(actor) barrier("done") } } } - diff --git a/akka-remote/src/multi-jvm/scala/akka/remote/NewRemoteActorMultiJvmSpec.scala b/akka-remote/src/multi-jvm/scala/akka/remote/NewRemoteActorMultiJvmSpec.scala index a91b203707..c3dc1ae9de 100644 --- a/akka-remote/src/multi-jvm/scala/akka/remote/NewRemoteActorMultiJvmSpec.scala +++ b/akka-remote/src/multi-jvm/scala/akka/remote/NewRemoteActorMultiJvmSpec.scala @@ -55,6 +55,9 @@ class NewRemoteActorMultiJvmNode2 extends AkkaRemoteSpec(NewRemoteActorMultiJvmS val actor = system.actorOf(Props[SomeActor], "service-hello") Await.result(actor ? "identify", timeout.duration).asInstanceOf[ActorRef].path.address.hostPort must equal(akkaSpec(0)) + // shut down the actor before we let the other node(s) shut down so we don't try to send + // "Terminate" to a shut down node + system.stop(actor) barrier("done") } } diff --git a/akka-remote/src/multi-jvm/scala/akka/remote/RandomRoutedRemoteActorMultiJvmSpec.scala b/akka-remote/src/multi-jvm/scala/akka/remote/RandomRoutedRemoteActorMultiJvmSpec.scala index bc69b2dd76..2b2b233dee 100644 --- a/akka-remote/src/multi-jvm/scala/akka/remote/RandomRoutedRemoteActorMultiJvmSpec.scala +++ b/akka-remote/src/multi-jvm/scala/akka/remote/RandomRoutedRemoteActorMultiJvmSpec.scala @@ -100,6 +100,9 @@ class RandomRoutedRemoteActorMultiJvmNode4 extends AkkaRemoteSpec(RandomRoutedRe barrier("end") replies.values foreach { _ must be > (0) } + // shut down the actor before we let the other node(s) shut down so we don't try to send + // "Terminate" to a shut down node + system.stop(actor) barrier("done") } } diff --git a/akka-remote/src/multi-jvm/scala/akka/remote/RoundRobinRoutedRemoteActorMultiJvmSpec.scala b/akka-remote/src/multi-jvm/scala/akka/remote/RoundRobinRoutedRemoteActorMultiJvmSpec.scala index 21c7e4cf64..c84aa46366 100644 --- a/akka-remote/src/multi-jvm/scala/akka/remote/RoundRobinRoutedRemoteActorMultiJvmSpec.scala +++ b/akka-remote/src/multi-jvm/scala/akka/remote/RoundRobinRoutedRemoteActorMultiJvmSpec.scala @@ -102,6 +102,9 @@ class RoundRobinRoutedRemoteActorMultiJvmNode4 extends AkkaRemoteSpec(RoundRobin barrier("end") replies.values foreach { _ must be(10) } + // shut down the actor before we let the other node(s) shut down so we don't try to send + // "Terminate" to a shut down node + system.stop(actor) barrier("done") } } diff --git a/project/AkkaBuild.scala b/project/AkkaBuild.scala index 5a62e6bdb5..f9fbfc6c4b 100644 --- a/project/AkkaBuild.scala +++ b/project/AkkaBuild.scala @@ -32,7 +32,7 @@ object AkkaBuild extends Build { Unidoc.unidocExclude := Seq(samples.id, tutorials.id), Dist.distExclude := Seq(actorTests.id, akkaSbtPlugin.id, docs.id) ), - aggregate = Seq(actor, testkit, actorTests, remote, cluster, slf4j, agent, transactor, mailboxes, zeroMQ, kernel, akkaSbtPlugin, actorMigration, samples, tutorials, docs) + aggregate = Seq(actor, testkit, actorTests, remote, camel, cluster, slf4j, agent, transactor, mailboxes, zeroMQ, kernel, akkaSbtPlugin, actorMigration, samples, tutorials, docs) ) lazy val actor = Project( @@ -239,6 +239,15 @@ object AkkaBuild extends Build { libraryDependencies ++= Dependencies.kernel ) ) + + lazy val camel = Project( + id = "akka-camel", + base = file("akka-camel"), + dependencies = Seq(actor, slf4j, testkit % "test->test"), + settings = defaultSettings ++ Seq( + libraryDependencies ++= Dependencies.camel + ) + ) lazy val actorMigration = Project( id = "akka-actor-migration", @@ -251,7 +260,8 @@ object AkkaBuild extends Build { id = "akka-sbt-plugin", base = file("akka-sbt-plugin"), settings = defaultSettings ++ Seq( - sbtPlugin := true + sbtPlugin := true, + scalaVersion := "2.9.1" ) ) @@ -465,6 +475,8 @@ object Dependencies { val kernel = Seq(Test.scalatest, Test.junit) + val camel = Seq(Test.scalatest, Test.junit, Test.mockito, camelCore) + // TODO: resolve Jetty version conflict // val sampleCamel = Seq(camelCore, camelSpring, commonsCodec, Runtime.camelJms, Runtime.activemq, Runtime.springJms, // Test.junit, Test.scalatest, Test.logback)