Merge branch 'master' of github.com:akka/akka
Signed-off-by: Jonas Bonér <jonas@jonasboner.com>
This commit is contained in:
commit
f7ca01a26b
105 changed files with 3239 additions and 4290 deletions
|
|
@ -555,14 +555,16 @@ private[akka] class ActorCell(
|
|||
def resume(): Unit = if (isNormal) dispatcher resume this
|
||||
|
||||
def link(subject: ActorRef): Unit = if (!isTerminating) {
|
||||
system.deathWatch.subscribe(self, subject)
|
||||
if (system.deathWatch.subscribe(self, subject)) {
|
||||
if (system.settings.DebugLifecycle) system.eventStream.publish(Debug(self.path.toString, clazz(actor), "now monitoring " + subject))
|
||||
}
|
||||
}
|
||||
|
||||
def unlink(subject: ActorRef): Unit = if (!isTerminating) {
|
||||
system.deathWatch.unsubscribe(self, subject)
|
||||
if (system.deathWatch.unsubscribe(self, subject)) {
|
||||
if (system.settings.DebugLifecycle) system.eventStream.publish(Debug(self.path.toString, clazz(actor), "stopped monitoring " + subject))
|
||||
}
|
||||
}
|
||||
|
||||
def terminate() {
|
||||
setReceiveTimeout(None)
|
||||
|
|
@ -691,7 +693,7 @@ private[akka] class ActorCell(
|
|||
parent.sendSystemMessage(ChildTerminated(self))
|
||||
system.deathWatch.publish(Terminated(self))
|
||||
if (system.settings.DebugLifecycle)
|
||||
system.eventStream.publish(Debug(self.path.toString, clazz(actor), "stopped")) // FIXME: can actor be null?
|
||||
system.eventStream.publish(Debug(self.path.toString, clazz(actor), "stopped"))
|
||||
} finally {
|
||||
if (a ne null) a.clearBehaviorStack()
|
||||
clearActorFields()
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2010 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camel;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Annotation used by implementations of {@link akka.actor.TypedActor}
|
||||
* (on method-level) to define consumer endpoints.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD})
|
||||
public @interface consume {
|
||||
|
||||
/**
|
||||
* Consumer endpoint URI
|
||||
*/
|
||||
public abstract String value();
|
||||
|
||||
/**
|
||||
* Route definition handler class for customizing route to annotated method.
|
||||
* The handler class must have a default constructor.
|
||||
*/
|
||||
public abstract Class<? extends RouteDefinitionHandler> routeDefinitionHandler()
|
||||
default RouteDefinitionIdentity.class;
|
||||
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
class=akka.camel.component.TypedActorComponent
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camel
|
||||
|
||||
import org.apache.camel.CamelContext
|
||||
|
||||
import akka.actor.Actor._
|
||||
import akka.actor._
|
||||
import akka.camel.component.TypedActorComponent
|
||||
|
||||
/**
|
||||
* Module that adds typed consumer actor support to akka-camel. It is automatically
|
||||
* detected by CamelService if added to the classpath.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
private[camel] object TypedCamel {
|
||||
private var consumerPublisher: ActorRef = _
|
||||
private var publishRequestor: ActorRef = _
|
||||
|
||||
/**
|
||||
* Adds the <code>TypedActorComponent</code> to <code>context</code>.
|
||||
*/
|
||||
def onCamelContextInit(context: CamelContext) {
|
||||
context.addComponent(TypedActorComponent.InternalSchema, new TypedActorComponent)
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures a <code>TypedConsumerPublishRequestor</code> and a <code>TypedConsumerPublisher</code>
|
||||
* and re-uses the <code>activationTracker</code> of <code>service</code>.
|
||||
*/
|
||||
def onCamelServiceStart(service: CamelService) {
|
||||
consumerPublisher = new LocalActorRef(Props(new TypedConsumerPublisher(service.activationTracker)), Props.randomName, true)
|
||||
publishRequestor = new LocalActorRef(Props(new TypedConsumerPublishRequestor), Props.randomName, true)
|
||||
|
||||
registerPublishRequestor
|
||||
|
||||
for (event ← PublishRequestor.pastActorRegisteredEvents) publishRequestor ! event
|
||||
publishRequestor ! InitPublishRequestor(consumerPublisher)
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the configured Configures <code>TypedConsumerPublishRequestor</code> and
|
||||
* <code>TypedConsumerPublisher</code>.
|
||||
*/
|
||||
def onCamelServiceStop(service: CamelService) {
|
||||
unregisterPublishRequestor
|
||||
consumerPublisher.stop
|
||||
}
|
||||
|
||||
private def registerPublishRequestor: Unit = registry.addListener(publishRequestor)
|
||||
private def unregisterPublishRequestor: Unit = registry.removeListener(publishRequestor)
|
||||
}
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camel
|
||||
|
||||
import java.lang.reflect.Method
|
||||
import java.lang.reflect.Proxy._
|
||||
|
||||
import akka.actor.{ LocalActorRef, TypedActor, ActorRef }
|
||||
import akka.actor.TypedActor._
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
private[camel] object TypedConsumer {
|
||||
|
||||
/**
|
||||
* Applies a function <code>f</code> to <code>actorRef</code> if <code>actorRef</code>
|
||||
* references a typed consumer actor. A valid reference to a typed consumer actor is a
|
||||
* local actor reference with a target actor that implements <code>TypedActor</code> and
|
||||
* has at least one of its methods annotated with <code>@consume</code> (on interface or
|
||||
* implementation class). For each <code>@consume</code>-annotated method, <code>f</code>
|
||||
* is called with the corresponding <code>method</code> instance and the return value is
|
||||
* added to a list which is then returned by this method.
|
||||
*/
|
||||
def withTypedConsumer[T](actorRef: ActorRef, typedActor: Option[AnyRef])(f: (AnyRef, Method) ⇒ T): List[T] = {
|
||||
typedActor match {
|
||||
case None ⇒ Nil
|
||||
case Some(tc) ⇒ {
|
||||
withConsumeAnnotatedMethodsOnInterfaces(tc, f) ++
|
||||
withConsumeAnnotatedMethodsonImplClass(tc, actorRef, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private implicit def class2ProxyClass(c: Class[_]) = new ProxyClass(c)
|
||||
|
||||
private def withConsumeAnnotatedMethodsOnInterfaces[T](tc: AnyRef, f: (AnyRef, Method) ⇒ T): List[T] = for {
|
||||
i ← tc.getClass.allInterfaces
|
||||
m ← i.getDeclaredMethods.toList
|
||||
if (m.isAnnotationPresent(classOf[consume]))
|
||||
} yield f(tc, m)
|
||||
|
||||
private def withConsumeAnnotatedMethodsonImplClass[T](tc: AnyRef, actorRef: ActorRef, f: (AnyRef, Method) ⇒ T): List[T] = actorRef match {
|
||||
case l: LocalActorRef ⇒
|
||||
val implClass = l.underlyingActorInstance.asInstanceOf[TypedActor.TypedActor[AnyRef, AnyRef]].me.getClass
|
||||
for (m ← implClass.getDeclaredMethods.toList; if (m.isAnnotationPresent(classOf[consume]))) yield f(tc, m)
|
||||
case _ ⇒ Nil
|
||||
}
|
||||
|
||||
private class ProxyClass(c: Class[_]) {
|
||||
def allInterfaces: List[Class[_]] = allInterfaces(c.getInterfaces.toList)
|
||||
def allInterfaces(is: List[Class[_]]): List[Class[_]] = is match {
|
||||
case Nil ⇒ Nil
|
||||
case x :: xs ⇒ x :: allInterfaces(x.getInterfaces.toList) ::: allInterfaces(xs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,139 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camel
|
||||
|
||||
import java.lang.reflect.Method
|
||||
|
||||
import akka.actor._
|
||||
import akka.camel.component.TypedActorComponent
|
||||
import akka.event.EventHandler
|
||||
|
||||
/**
|
||||
* Concrete publish requestor that requests publication of typed consumer actor methods on
|
||||
* <code>TypedActorRegistered</code> events and unpublication of typed consumer actor methods on
|
||||
* <code>TypedActorUnregistered</code> events.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
private[camel] class TypedConsumerPublishRequestor extends PublishRequestor {
|
||||
def receiveActorRegistryEvent = {
|
||||
case TypedActorRegistered(_, actor, typedActor) ⇒ for (event ← ConsumerMethodRegistered.eventsFor(actor, Option(typedActor))) deliverCurrentEvent(event)
|
||||
case TypedActorUnregistered(_, actor, typedActor) ⇒ for (event ← ConsumerMethodUnregistered.eventsFor(actor, Option(typedActor))) deliverCurrentEvent(event)
|
||||
case _ ⇒ ()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Publishes a typed consumer actor method on <code>ConsumerMethodRegistered</code> events and
|
||||
* unpublishes a typed consumer actor method on <code>ConsumerMethodUnregistered</code> events.
|
||||
* Publications are tracked by sending an <code>activationTracker</code> an <code>EndpointActivated</code>
|
||||
* event, unpublications are tracked by sending an <code>EndpointActivated</code> event.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
private[camel] class TypedConsumerPublisher(activationTracker: ActorRef) extends Actor {
|
||||
import TypedConsumerPublisher._
|
||||
|
||||
def receive = {
|
||||
case mr: ConsumerMethodRegistered ⇒ {
|
||||
handleConsumerMethodRegistered(mr)
|
||||
activationTracker ! EndpointActivated
|
||||
}
|
||||
case mu: ConsumerMethodUnregistered ⇒ {
|
||||
handleConsumerMethodUnregistered(mu)
|
||||
activationTracker ! EndpointDeactivated
|
||||
}
|
||||
case _ ⇒ { /* ignore */ }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
private[camel] object TypedConsumerPublisher {
|
||||
/**
|
||||
* Creates a route to a typed actor method.
|
||||
*/
|
||||
def handleConsumerMethodRegistered(event: ConsumerMethodRegistered) {
|
||||
CamelContextManager.mandatoryContext.addRoutes(new ConsumerMethodRouteBuilder(event))
|
||||
EventHandler.info(this, "published method %s of %s at endpoint %s" format (event.methodName, event.typedActor, event.endpointUri))
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the route to the already un-registered typed consumer actor method.
|
||||
*/
|
||||
def handleConsumerMethodUnregistered(event: ConsumerMethodUnregistered) {
|
||||
CamelContextManager.mandatoryContext.stopRoute(event.methodUuid)
|
||||
EventHandler.info(this, "unpublished method %s of %s from endpoint %s" format (event.methodName, event.typedActor, event.endpointUri))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder of a route to a typed consumer actor method.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
private[camel] class ConsumerMethodRouteBuilder(event: ConsumerMethodRegistered) extends ConsumerRouteBuilder(event.endpointUri, event.methodUuid) {
|
||||
protected def routeDefinitionHandler: RouteDefinitionHandler = event.routeDefinitionHandler
|
||||
protected def targetUri = "%s:%s?method=%s" format (TypedActorComponent.InternalSchema, event.methodUuid, event.methodName)
|
||||
}
|
||||
|
||||
/**
|
||||
* A typed consumer method (un)registration event.
|
||||
*/
|
||||
private[camel] trait ConsumerMethodEvent extends ConsumerEvent {
|
||||
val actorRef: ActorRef
|
||||
val typedActor: AnyRef
|
||||
val method: Method
|
||||
|
||||
val methodName = method.getName
|
||||
val methodUuid = "%s_%s" format (uuid, methodName)
|
||||
|
||||
lazy val routeDefinitionHandler = consumeAnnotation.routeDefinitionHandler.newInstance
|
||||
lazy val consumeAnnotation = method.getAnnotation(classOf[consume])
|
||||
lazy val endpointUri = consumeAnnotation.value
|
||||
}
|
||||
|
||||
/**
|
||||
* Event indicating that a typed consumer actor has been registered at the actor registry. For
|
||||
* each <code>@consume</code> annotated typed actor method a separate event is created.
|
||||
*/
|
||||
private[camel] case class ConsumerMethodRegistered(actorRef: ActorRef, typedActor: AnyRef, method: Method) extends ConsumerMethodEvent
|
||||
|
||||
/**
|
||||
* Event indicating that a typed consumer actor has been unregistered from the actor registry. For
|
||||
* each <code>@consume</code> annotated typed actor method a separate event is created.
|
||||
*/
|
||||
private[camel] case class ConsumerMethodUnregistered(actorRef: ActorRef, typedActor: AnyRef, method: Method) extends ConsumerMethodEvent
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
private[camel] object ConsumerMethodRegistered {
|
||||
/**
|
||||
* Creates a list of ConsumerMethodRegistered event messages for a typed consumer actor or an empty
|
||||
* list if <code>actorRef</code> doesn't reference a typed consumer actor.
|
||||
*/
|
||||
def eventsFor(actorRef: ActorRef, typedActor: Option[AnyRef]): List[ConsumerMethodRegistered] = {
|
||||
TypedConsumer.withTypedConsumer(actorRef, typedActor) { (tc, m) ⇒
|
||||
ConsumerMethodRegistered(actorRef, tc, m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
private[camel] object ConsumerMethodUnregistered {
|
||||
/**
|
||||
* Creates a list of ConsumerMethodUnregistered event messages for a typed consumer actor or an empty
|
||||
* list if <code>actorRef</code> doesn't reference a typed consumer actor.
|
||||
*/
|
||||
def eventsFor(actorRef: ActorRef, typedActor: Option[AnyRef]): List[ConsumerMethodUnregistered] = {
|
||||
TypedConsumer.withTypedConsumer(actorRef, typedActor) { (tc, m) ⇒
|
||||
ConsumerMethodUnregistered(actorRef, tc, m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2010 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camel.component
|
||||
|
||||
import java.util.Map
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
import org.apache.camel.CamelContext
|
||||
import org.apache.camel.component.bean._
|
||||
|
||||
import akka.actor._
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
object TypedActorComponent {
|
||||
/**
|
||||
* Default schema name for typed actor endpoint URIs.
|
||||
*/
|
||||
val InternalSchema = "typed-actor-internal"
|
||||
}
|
||||
|
||||
/**
|
||||
* Camel component for exchanging messages with typed actors. This component
|
||||
* tries to obtain the typed actor from <code>Actor.registry</code> if the
|
||||
* schema is <code>TypedActorComponent.InternalSchema</code>. If the schema
|
||||
* name is <code>typed-actor</code> this component tries to obtain the typed
|
||||
* actor from the CamelContext's registry.
|
||||
*
|
||||
* @see org.apache.camel.component.bean.BeanComponent
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
class TypedActorComponent extends BeanComponent {
|
||||
val typedActorRegistry = new ConcurrentHashMap[String, AnyRef]
|
||||
|
||||
/**
|
||||
* Creates an <code>org.apache.camel.component.bean.BeanEndpoint</code> with a custom
|
||||
* bean holder that uses <code>Actor.registry</code> for getting access to typed actors
|
||||
* (beans).
|
||||
*
|
||||
* @see akka.camel.component.TypedActorHolder
|
||||
*/
|
||||
override def createEndpoint(uri: String, remaining: String, parameters: Map[String, AnyRef]) = {
|
||||
val endpoint = new BeanEndpoint(uri, this)
|
||||
endpoint.setBeanName(remaining)
|
||||
endpoint.setBeanHolder(createBeanHolder(uri, remaining))
|
||||
setProperties(endpoint.getProcessor, parameters)
|
||||
endpoint
|
||||
}
|
||||
|
||||
private def createBeanHolder(uri: String, beanName: String) =
|
||||
new TypedActorHolder(uri, getCamelContext, beanName).createCacheHolder
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>org.apache.camel.component.bean.BeanHolder</code> implementation that uses
|
||||
* <code>Actor.registry</code> for getting access to typed actors.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
class TypedActorHolder(uri: String, context: CamelContext, name: String)
|
||||
extends RegistryBean(context, name) {
|
||||
|
||||
/**
|
||||
* Returns an <code>akka.camel.component.BeanInfo</code> instance.
|
||||
*/
|
||||
override def getBeanInfo: BeanInfo =
|
||||
new BeanInfo(getContext, getBean.getClass, getParameterMappingStrategy)
|
||||
|
||||
/**
|
||||
* Obtains a typed actor from <code>Actor.registry</code> if the schema is
|
||||
* <code>TypedActorComponent.InternalSchema</code>. If the schema name is
|
||||
* <code>typed-actor</code> this method obtains the typed actor from the
|
||||
* CamelContext's registry.
|
||||
*
|
||||
* @return a typed actor or <code>null</code>.
|
||||
*/
|
||||
override def getBean: AnyRef = {
|
||||
val internal = uri.startsWith(TypedActorComponent.InternalSchema)
|
||||
if (internal) Actor.registry.local.typedActorFor(uuidFrom(getName)) getOrElse null else super.getBean
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
package akka.camel;
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
public interface SampleTypedActor {
|
||||
|
||||
public String foo(String s);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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) {
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
package akka.camel;
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
public class SampleTypedSingleConsumerImpl implements SampleTypedSingleConsumer {
|
||||
|
||||
public void foo(String b) {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
package akka.camel
|
||||
|
||||
import org.scalatest.junit.JUnitSuite
|
||||
|
||||
class TypedConsumerJavaTest extends TypedConsumerJavaTestBase with JUnitSuite
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
class=akka.camel.component.ActorComponent
|
||||
94
akka-camel/src/main/scala/akka/camel/Activation.scala
Normal file
94
akka-camel/src/main/scala/akka/camel/Activation.scala
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camel
|
||||
|
||||
import internal._
|
||||
import akka.util.{ Timeout, Duration }
|
||||
import akka.dispatch.Future
|
||||
import java.util.concurrent.TimeoutException
|
||||
import akka.actor.{ ActorSystem, Props, ActorRef }
|
||||
import akka.pattern._
|
||||
|
||||
/**
|
||||
* Activation trait that can be used to wait on activation or de-activation of Camel endpoints.
|
||||
* The Camel endpoints are activated asynchronously. This trait can signal when an endpoint is activated or de-activated.
|
||||
*/
|
||||
trait Activation {
|
||||
import akka.dispatch.Await
|
||||
|
||||
def system: ActorSystem
|
||||
|
||||
private val activationTracker = system.actorOf(Props[ActivationTracker])
|
||||
|
||||
/**
|
||||
* Awaits for endpoint to be activated. It blocks until the endpoint is registered in camel context or timeout expires.
|
||||
* @param endpoint the endpoint to wait for to be activated
|
||||
* @param timeout the timeout for the wait
|
||||
* @throws akka.camel.ActivationTimeoutException if endpoint is not activated within timeout.
|
||||
*/
|
||||
def awaitActivation(endpoint: ActorRef, timeout: Duration): ActorRef = {
|
||||
try {
|
||||
Await.result(activationFutureFor(endpoint, timeout), timeout)
|
||||
} catch {
|
||||
case e: TimeoutException ⇒ throw new ActivationTimeoutException(endpoint, timeout)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Awaits for endpoint to be de-activated. It is blocking until endpoint is unregistered in camel context or timeout expires.
|
||||
* @param endpoint the endpoint to wait for to be de-activated
|
||||
* @param timeout the timeout for the wait
|
||||
* @throws akka.camel.DeActivationTimeoutException if endpoint is not de-activated within timeout.
|
||||
*/
|
||||
def awaitDeactivation(endpoint: ActorRef, timeout: Duration) {
|
||||
try {
|
||||
Await.result(deactivationFutureFor(endpoint, timeout), timeout)
|
||||
} catch {
|
||||
case e: TimeoutException ⇒ throw new DeActivationTimeoutException(endpoint, timeout)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to `awaitActivation` but returns a future instead.
|
||||
* @param endpoint the endpoint to be activated
|
||||
* @param timeout the timeout for the Future
|
||||
*/
|
||||
def activationFutureFor(endpoint: ActorRef, timeout: Duration): Future[ActorRef] = {
|
||||
(activationTracker.ask(AwaitActivation(endpoint))(Timeout(timeout))).map[ActorRef] {
|
||||
case EndpointActivated(_) ⇒ endpoint
|
||||
case EndpointFailedToActivate(_, cause) ⇒ throw cause
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to awaitDeactivation but returns a future instead.
|
||||
* @param endpoint the endpoint to be deactivated
|
||||
* @param timeout the timeout of the Future
|
||||
*/
|
||||
def deactivationFutureFor(endpoint: ActorRef, timeout: Duration): Future[Unit] = {
|
||||
(activationTracker.ask(AwaitDeActivation(endpoint))(Timeout(timeout))).map[Unit] {
|
||||
case EndpointDeActivated(_) ⇒ ()
|
||||
case EndpointFailedToDeActivate(_, cause) ⇒ throw cause
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An exception for when a timeout has occurred during deactivation of an endpoint
|
||||
* @param endpoint the endpoint that could not be de-activated in time
|
||||
* @param timeout the timeout
|
||||
*/
|
||||
class DeActivationTimeoutException(endpoint: ActorRef, timeout: Duration) extends TimeoutException {
|
||||
override def getMessage = "Timed out after %s, while waiting for de-activation of %s" format (timeout, endpoint.path)
|
||||
}
|
||||
|
||||
/**
|
||||
* An exception for when a timeout has occurred during the activation of an endpoint
|
||||
* @param endpoint the endpoint that could not be activated in time
|
||||
* @param timeout the timeout
|
||||
*/
|
||||
class ActivationTimeoutException(endpoint: ActorRef, timeout: Duration) extends TimeoutException {
|
||||
override def getMessage = "Timed out after %s, while waiting for activation of %s" format (timeout, endpoint.path)
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camel
|
||||
|
||||
import internal.component.ActorEndpointPath
|
||||
import akka.actor.ActorRef
|
||||
import org.apache.camel.model.ProcessorDefinition
|
||||
|
||||
class ActorRouteDefinition(definition: ProcessorDefinition[_]) {
|
||||
def to(actorRef: ActorRef) = definition.to(ActorEndpointPath(actorRef).toCamelPath())
|
||||
def to(actorRef: ActorRef, consumerConfig: ConsumerConfig) = definition.to(ActorEndpointPath(actorRef).toCamelPath(consumerConfig))
|
||||
}
|
||||
|
||||
63
akka-camel/src/main/scala/akka/camel/Camel.scala
Normal file
63
akka-camel/src/main/scala/akka/camel/Camel.scala
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camel
|
||||
|
||||
import internal._
|
||||
import akka.actor._
|
||||
import org.apache.camel.{ ProducerTemplate, CamelContext }
|
||||
|
||||
//TODO complete this doc
|
||||
/**
|
||||
* Camel trait encapsulates the underlying camel machinery.
|
||||
* '''Note:''' `CamelContext` and `ProducerTemplate` are stopped when the associated actor system is shut down.
|
||||
*
|
||||
*/
|
||||
trait Camel extends ConsumerRegistry with ProducerRegistry with Extension with Activation {
|
||||
/**
|
||||
* Underlying camel context.
|
||||
*
|
||||
* It can be used to configure camel manually, i.e. when the user wants to add new routes or endpoints,
|
||||
* i.e. <pre>camel.context.addRoutes(...)</pre>
|
||||
*
|
||||
* @see [[org.apache.camel.CamelContext]]
|
||||
*/
|
||||
def context: CamelContext
|
||||
|
||||
/**
|
||||
* Producer template.
|
||||
* @see [[org.apache.camel.ProducerTemplate]]
|
||||
*/
|
||||
def template: ProducerTemplate
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This class can be used to get hold of an instance of the Camel class bound to the actor system.
|
||||
* <p>For example:
|
||||
* {{{
|
||||
* val system = ActorSystem("some system")
|
||||
* val camel = CamelExtension(system)
|
||||
* camel.context.addRoutes(...)
|
||||
* }}}
|
||||
*
|
||||
* @see akka.actor.ExtensionId
|
||||
* @see akka.actor.ExtensionIdProvider
|
||||
*
|
||||
*/
|
||||
object CamelExtension extends ExtensionId[Camel] with ExtensionIdProvider {
|
||||
|
||||
/**
|
||||
* Creates a new instance of Camel and makes sure it gets stopped when the actor system is shutdown.
|
||||
*/
|
||||
def createExtension(system: ExtendedActorSystem) = {
|
||||
val camel = new DefaultCamel(system).start
|
||||
system.registerOnTermination(camel.shutdown())
|
||||
camel
|
||||
}
|
||||
|
||||
def lookup(): ExtensionId[Camel] = CamelExtension
|
||||
|
||||
override def get(system: ActorSystem): Camel = super.get(system)
|
||||
}
|
||||
|
|
@ -1,186 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2010 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camel
|
||||
|
||||
import org.apache.camel.{ ProducerTemplate, CamelContext }
|
||||
import org.apache.camel.impl.DefaultCamelContext
|
||||
|
||||
import akka.event.EventHandler
|
||||
import akka.japi.{ Option ⇒ JOption }
|
||||
|
||||
import TypedCamelAccess._
|
||||
|
||||
/**
|
||||
* Manages the lifecycle of a CamelContext. Allowed transitions are
|
||||
* init -> start -> stop -> init -> ... etc.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
trait CamelContextLifecycle {
|
||||
// TODO: enforce correct state transitions
|
||||
// valid: init -> start -> stop -> init ...
|
||||
|
||||
private var _context: Option[CamelContext] = None
|
||||
private var _template: Option[ProducerTemplate] = None
|
||||
|
||||
private var _initialized = false
|
||||
private var _started = false
|
||||
|
||||
/**
|
||||
* Returns <code>Some(CamelContext)</code> (containing the current CamelContext)
|
||||
* if <code>CamelContextLifecycle</code> has been initialized, otherwise <code>None</code>.
|
||||
*/
|
||||
def context: Option[CamelContext] = _context
|
||||
|
||||
/**
|
||||
* Returns <code>Some(ProducerTemplate)</code> (containing the current ProducerTemplate)
|
||||
* if <code>CamelContextLifecycle</code> has been initialized, otherwise <code>None</code>.
|
||||
*/
|
||||
def template: Option[ProducerTemplate] = _template
|
||||
|
||||
/**
|
||||
* Returns <code>Some(CamelContext)</code> (containing the current CamelContext)
|
||||
* if <code>CamelContextLifecycle</code> has been initialized, otherwise <code>None</code>.
|
||||
* <p>
|
||||
* Java API.
|
||||
*/
|
||||
def getContext: JOption[CamelContext] = context
|
||||
|
||||
/**
|
||||
* Returns <code>Some(ProducerTemplate)</code> (containing the current ProducerTemplate)
|
||||
* if <code>CamelContextLifecycle</code> has been initialized, otherwise <code>None</code>.
|
||||
* <p>
|
||||
* Java API.
|
||||
*/
|
||||
def getTemplate: JOption[ProducerTemplate] = template
|
||||
|
||||
/**
|
||||
* Returns the current <code>CamelContext</code> if this <code>CamelContextLifecycle</code>
|
||||
* has been initialized, otherwise throws an <code>IllegalStateException</code>.
|
||||
*/
|
||||
def mandatoryContext =
|
||||
if (context.isDefined) context.get
|
||||
else throw new IllegalStateException("no current CamelContext")
|
||||
|
||||
/**
|
||||
* Returns the current <code>ProducerTemplate</code> if this <code>CamelContextLifecycle</code>
|
||||
* has been initialized, otherwise throws an <code>IllegalStateException</code>.
|
||||
*/
|
||||
def mandatoryTemplate =
|
||||
if (template.isDefined) template.get
|
||||
else throw new IllegalStateException("no current ProducerTemplate")
|
||||
|
||||
/**
|
||||
* Returns the current <code>CamelContext</code> if this <code>CamelContextLifecycle</code>
|
||||
* has been initialized, otherwise throws an <code>IllegalStateException</code>.
|
||||
* <p>
|
||||
* Java API.
|
||||
*/
|
||||
def getMandatoryContext = mandatoryContext
|
||||
|
||||
/**
|
||||
* Returns the current <code>ProducerTemplate</code> if this <code>CamelContextLifecycle</code>
|
||||
* has been initialized, otherwise throws an <code>IllegalStateException</code>.
|
||||
* <p>
|
||||
* Java API.
|
||||
*/
|
||||
def getMandatoryTemplate = mandatoryTemplate
|
||||
|
||||
def initialized = _initialized
|
||||
def started = _started
|
||||
|
||||
/**
|
||||
* Starts the CamelContext and an associated ProducerTemplate.
|
||||
*/
|
||||
def start = {
|
||||
for {
|
||||
c ← context
|
||||
t ← template
|
||||
} {
|
||||
c.start()
|
||||
t.start()
|
||||
_started = true
|
||||
EventHandler.info(this, "Camel context started")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the CamelContext and the associated ProducerTemplate.
|
||||
*/
|
||||
def stop = {
|
||||
for {
|
||||
t ← template
|
||||
c ← context
|
||||
} {
|
||||
t.stop
|
||||
c.stop
|
||||
_started = false
|
||||
_initialized = false
|
||||
EventHandler.info(this, "Camel context stopped")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this instance a new DefaultCamelContext.
|
||||
*/
|
||||
def init(): Unit = init(new DefaultCamelContext)
|
||||
|
||||
/**
|
||||
* Initializes this instance with the given CamelContext. For the passed <code>context</code>
|
||||
* stream-caching is enabled. If applications want to disable stream-caching they can do so
|
||||
* after this method returned and prior to calling start.
|
||||
*/
|
||||
def init(context: CamelContext) {
|
||||
context.setStreamCaching(true)
|
||||
|
||||
for (tc ← TypedCamelModule.typedCamelObject) tc.onCamelContextInit(context)
|
||||
|
||||
this._context = Some(context)
|
||||
this._template = Some(context.createProducerTemplate)
|
||||
|
||||
_initialized = true
|
||||
EventHandler.info(this, "Camel context initialized")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages a global CamelContext and an associated ProducerTemplate.
|
||||
*/
|
||||
object CamelContextManager extends CamelContextLifecycle {
|
||||
|
||||
// -----------------------------------------------------
|
||||
// The inherited getters aren't statically accessible
|
||||
// from Java. Therefore, they are redefined here.
|
||||
// TODO: investigate if this is a Scala bug.
|
||||
// -----------------------------------------------------
|
||||
|
||||
/**
|
||||
* see CamelContextLifecycle.getContext
|
||||
* <p>
|
||||
* Java API.
|
||||
*/
|
||||
override def getContext: JOption[CamelContext] = super.getContext
|
||||
|
||||
/**
|
||||
* see CamelContextLifecycle.getTemplate
|
||||
* <p>
|
||||
* Java API.
|
||||
*/
|
||||
override def getTemplate: JOption[ProducerTemplate] = super.getTemplate
|
||||
|
||||
/**
|
||||
* see CamelContextLifecycle.getMandatoryContext
|
||||
* <p>
|
||||
* Java API.
|
||||
*/
|
||||
override def getMandatoryContext = super.getMandatoryContext
|
||||
|
||||
/**
|
||||
* see CamelContextLifecycle.getMandatoryTemplate
|
||||
* <p>
|
||||
* Java API.
|
||||
*/
|
||||
override def getMandatoryTemplate = super.getMandatoryTemplate
|
||||
}
|
||||
266
akka-camel/src/main/scala/akka/camel/CamelMessage.scala
Normal file
266
akka-camel/src/main/scala/akka/camel/CamelMessage.scala
Normal file
|
|
@ -0,0 +1,266 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camel
|
||||
|
||||
import java.util.{ Map ⇒ JMap, Set ⇒ JSet }
|
||||
|
||||
import scala.collection.JavaConversions._
|
||||
|
||||
import akka.japi.{ Function ⇒ JFunction }
|
||||
import org.apache.camel.{ CamelContext, Message ⇒ JCamelMessage }
|
||||
|
||||
/**
|
||||
* An immutable representation of a Camel message.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
case class CamelMessage(body: Any, headers: Map[String, Any]) {
|
||||
|
||||
def this(body: Any, headers: JMap[String, Any]) = this(body, headers.toMap) //for Java
|
||||
|
||||
override def toString = "CamelMessage(%s, %s)" format (body, headers)
|
||||
|
||||
/**
|
||||
* Returns those headers from this message whose name is contained in <code>names</code>.
|
||||
*/
|
||||
def headers(names: Set[String]): Map[String, Any] = headers.filterKeys(names contains _)
|
||||
|
||||
/**
|
||||
* Returns those headers from this message whose name is contained in <code>names</code>.
|
||||
* The returned headers map is backed up by an immutable headers map. Any attempt to modify
|
||||
* the returned map will throw an exception.
|
||||
* <p>
|
||||
* Java API
|
||||
*/
|
||||
def getHeaders(names: JSet[String]): JMap[String, Any] = headers(names.toSet)
|
||||
|
||||
/**
|
||||
* Returns all headers from this message. The returned headers map is backed up by this
|
||||
* message's immutable headers map. Any attempt to modify the returned map will throw an
|
||||
* exception.
|
||||
* <p>
|
||||
* Java API
|
||||
*/
|
||||
def getHeaders: JMap[String, Any] = headers
|
||||
|
||||
/**
|
||||
* Returns the header with given <code>name</code>. Throws <code>NoSuchElementException</code>
|
||||
* if the header doesn't exist.
|
||||
*/
|
||||
def header(name: String): Option[Any] = headers.get(name)
|
||||
|
||||
/**
|
||||
* Returns the header with given <code>name</code>. Throws <code>NoSuchElementException</code>
|
||||
* if the header doesn't exist.
|
||||
* <p>
|
||||
* Java API
|
||||
*/
|
||||
def getHeader(name: String): Any = headers(name)
|
||||
|
||||
/**
|
||||
* Creates a CamelMessage with a transformed body using a <code>transformer</code> function.
|
||||
*/
|
||||
def mapBody[A, B](transformer: A ⇒ B): CamelMessage = withBody(transformer(body.asInstanceOf[A]))
|
||||
|
||||
/**
|
||||
* Creates a CamelMessage with a transformed body using a <code>transformer</code> function.
|
||||
* <p>
|
||||
* Java API
|
||||
*/
|
||||
def mapBody[A, B](transformer: JFunction[A, B]): CamelMessage = withBody(transformer(body.asInstanceOf[A]))
|
||||
|
||||
/**
|
||||
* Creates a CamelMessage with a given <code>body</code>.
|
||||
*/
|
||||
def withBody(body: Any) = CamelMessage(body, this.headers)
|
||||
|
||||
/**
|
||||
* Creates a new CamelMessage with given <code>headers</code>.
|
||||
*/
|
||||
def withHeaders[A](headers: Map[String, A]): CamelMessage = copy(this.body, headers)
|
||||
|
||||
/**
|
||||
* Creates a new CamelMessage with given <code>headers</code>. A copy of the headers map is made.
|
||||
* <p>
|
||||
* Java API
|
||||
*/
|
||||
def withHeaders[A](headers: JMap[String, A]): CamelMessage = withHeaders(headers.toMap)
|
||||
|
||||
/**
|
||||
* Creates a new CamelMessage with given <code>headers</code> added to the current headers.
|
||||
*/
|
||||
def addHeaders[A](headers: Map[String, A]): CamelMessage = copy(this.body, this.headers ++ headers)
|
||||
|
||||
/**
|
||||
* Creates a new CamelMessage with given <code>headers</code> added to the current headers.
|
||||
* A copy of the headers map is made.
|
||||
* <p>
|
||||
* Java API
|
||||
*/
|
||||
def addHeaders[A](headers: JMap[String, A]): CamelMessage = addHeaders(headers.toMap)
|
||||
|
||||
/**
|
||||
* Creates a new CamelMessage with the given <code>header</code> added to the current headers.
|
||||
*/
|
||||
def addHeader(header: (String, Any)): CamelMessage = copy(this.body, this.headers + header)
|
||||
|
||||
/**
|
||||
* Creates a new CamelMessage with the given header, represented by <code>name</code> and
|
||||
* <code>value</code> added to the existing headers.
|
||||
* <p>
|
||||
* Java API
|
||||
*/
|
||||
def addHeader(name: String, value: Any): CamelMessage = addHeader((name, value))
|
||||
|
||||
/**
|
||||
* Creates a new CamelMessage where the header with given <code>headerName</code> is removed from
|
||||
* the existing headers.
|
||||
*/
|
||||
def withoutHeader(headerName: String) = copy(this.body, this.headers - headerName)
|
||||
|
||||
def copyContentTo(to: JCamelMessage) = {
|
||||
to.setBody(this.body)
|
||||
for ((name, value) ← this.headers) to.getHeaders.put(name, value.asInstanceOf[AnyRef])
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the body of the message converted to the type <code>T</code>. Conversion is done
|
||||
* using Camel's type converter. The type converter is obtained from the CamelContext managed
|
||||
* by CamelContextManager. Applications have to ensure proper initialization of
|
||||
* CamelContextManager.
|
||||
*
|
||||
* @see CamelContextManager.
|
||||
*/
|
||||
|
||||
def bodyAs[T](implicit m: Manifest[T], camelContext: CamelContext): T = getBodyAs(m.erasure.asInstanceOf[Class[T]], camelContext)
|
||||
|
||||
/**
|
||||
* Returns the body of the message converted to the type as given by the <code>clazz</code>
|
||||
* parameter. Conversion is done using Camel's type converter. The type converter is obtained
|
||||
* from the CamelContext managed by CamelContextManager. Applications have to ensure proper
|
||||
* initialization of CamelContextManager.
|
||||
* <p>
|
||||
* Java API
|
||||
*
|
||||
* @see CamelContextManager.
|
||||
*/
|
||||
def getBodyAs[T](clazz: Class[T], camelContext: CamelContext): T =
|
||||
camelContext.getTypeConverter.mandatoryConvertTo[T](clazz, body)
|
||||
|
||||
/**
|
||||
* Creates a CamelMessage with current <code>body</code> converted to type <code>T</code>.
|
||||
*/
|
||||
def withBodyAs[T](implicit m: Manifest[T], camelContext: CamelContext): CamelMessage = withBodyAs(m.erasure.asInstanceOf[Class[T]])
|
||||
|
||||
/**
|
||||
* Creates a CamelMessage with current <code>body</code> converted to type <code>clazz</code>.
|
||||
* <p>
|
||||
* Java API
|
||||
*/
|
||||
def withBodyAs[T](clazz: Class[T])(implicit camelContext: CamelContext): CamelMessage = withBody(getBodyAs(clazz, camelContext))
|
||||
|
||||
/**
|
||||
* Returns the header with given <code>name</code> converted to type <code>T</code>. Throws
|
||||
* <code>NoSuchElementException</code> if the header doesn't exist.
|
||||
*/
|
||||
def headerAs[T](name: String)(implicit m: Manifest[T], camelContext: CamelContext): Option[T] = header(name).map(camelContext.getTypeConverter.mandatoryConvertTo[T](m.erasure.asInstanceOf[Class[T]], _))
|
||||
|
||||
/**
|
||||
* Returns the header with given <code>name</code> converted to type as given by the <code>clazz</code>
|
||||
* parameter. Throws <code>NoSuchElementException</code> if the header doesn't exist.
|
||||
* <p>
|
||||
* Java API
|
||||
*/
|
||||
def getHeaderAs[T](name: String, clazz: Class[T], camelContext: CamelContext) = headerAs[T](name)(Manifest.classType(clazz), camelContext).get
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Companion object of CamelMessage class.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
object CamelMessage {
|
||||
|
||||
/**
|
||||
* CamelMessage header to correlate request with response messages. Applications that send
|
||||
* messages to a Producer actor may want to set this header on the request message
|
||||
* so that it can be correlated with an asynchronous response. Messages send to Consumer
|
||||
* actors have this header already set.
|
||||
*/
|
||||
val MessageExchangeId = "MessageExchangeId".intern
|
||||
|
||||
/**
|
||||
* Creates a canonical form of the given message <code>msg</code>. If <code>msg</code> of type
|
||||
* CamelMessage then <code>msg</code> is returned, otherwise <code>msg</code> is set as body of a
|
||||
* newly created CamelMessage object.
|
||||
*/
|
||||
def canonicalize(msg: Any) = msg match {
|
||||
case mobj: CamelMessage ⇒ mobj
|
||||
case body ⇒ CamelMessage(body, Map.empty)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new CamelMessage object from the Camel message.
|
||||
*/
|
||||
def from(camelMessage: JCamelMessage): CamelMessage = from(camelMessage, Map.empty)
|
||||
|
||||
/**
|
||||
* Creates a new CamelMessage object from the Camel message.
|
||||
*
|
||||
* @param headers additional headers to set on the created CamelMessage in addition to those
|
||||
* in the Camel message.
|
||||
*/
|
||||
def from(camelMessage: JCamelMessage, headers: Map[String, Any]): CamelMessage = CamelMessage(camelMessage.getBody, headers ++ camelMessage.getHeaders)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Positive acknowledgement message (used for application-acknowledged message receipts).
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
case object Ack {
|
||||
/** Java API to get the Ack singleton */
|
||||
def ack = this
|
||||
}
|
||||
|
||||
/**
|
||||
* An immutable representation of a failed Camel exchange. It contains the failure cause
|
||||
* obtained from Exchange.getException and the headers from either the Exchange.getIn
|
||||
* message or Exchange.getOut message, depending on the exchange pattern.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
case class Failure(val cause: Throwable, val headers: Map[String, Any] = Map.empty) {
|
||||
|
||||
/**
|
||||
* Creates a Failure with cause body and empty headers map.
|
||||
*/
|
||||
def this(cause: Throwable) = this(cause, Map.empty[String, Any])
|
||||
|
||||
/**
|
||||
* Creates a Failure with given cause and headers map. A copy of the headers map is made.
|
||||
* <p>
|
||||
* Java API
|
||||
*/
|
||||
def this(cause: Throwable, headers: JMap[String, Any]) = this(cause, headers.toMap)
|
||||
|
||||
/**
|
||||
* Returns the cause of this Failure.
|
||||
* <p>
|
||||
* Java API.
|
||||
*/
|
||||
def getCause = cause
|
||||
|
||||
/**
|
||||
* Returns all headers from this failure message. The returned headers map is backed up by
|
||||
* this message's immutable headers map. Any attempt to modify the returned map will throw
|
||||
* an exception.
|
||||
* <p>
|
||||
* Java API
|
||||
*/
|
||||
def getHeaders: JMap[String, Any] = headers
|
||||
}
|
||||
|
|
@ -1,268 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2010 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
package akka.camel
|
||||
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
import org.apache.camel.CamelContext
|
||||
|
||||
import akka.actor.{ newUuid, Props, LocalActorRef, Actor }
|
||||
import akka.config.Config._
|
||||
import akka.japi.{ SideEffect, Option ⇒ JOption }
|
||||
import akka.util.Bootable
|
||||
|
||||
import TypedCamelAccess._
|
||||
import akka.dispatch.Await
|
||||
|
||||
/**
|
||||
* Publishes consumer actors at their Camel endpoints. Consumer actors are published asynchronously when
|
||||
* they are started and un-published asynchronously when they are stopped. The CamelService is notified
|
||||
* about actor life cycle by registering listeners at Actor.registry.
|
||||
* <p>
|
||||
* If the akka-camel-typed jar is on the classpath, it is automatically detected by the CamelService. The
|
||||
* optional akka-camel-typed jar provides support for typed consumer actors.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
trait CamelService extends Bootable {
|
||||
private[camel] val activationTracker = new LocalActorRef(Props[ActivationTracker], Props.randomName, true)
|
||||
private[camel] val consumerPublisher = new LocalActorRef(Props(new ConsumerPublisher(activationTracker)), Props.randomName, true)
|
||||
private[camel] val publishRequestor = new LocalActorRef(Props(new ConsumerPublishRequestor), Props.randomName, true)
|
||||
|
||||
private val serviceEnabled = config.getList("akka.enabled-modules").exists(_ == "camel")
|
||||
|
||||
/**
|
||||
* Starts this CamelService if the <code>akka.enabled-modules</code> list contains <code>"camel"</code>.
|
||||
*/
|
||||
abstract override def onLoad = {
|
||||
super.onLoad
|
||||
if (serviceEnabled) start
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops this CamelService if the <code>akka.enabled-modules</code> list contains <code>"camel"</code>.
|
||||
*/
|
||||
abstract override def onUnload = {
|
||||
if (serviceEnabled) stop
|
||||
super.onUnload
|
||||
}
|
||||
|
||||
@deprecated("use start() instead", "1.1")
|
||||
def load = start
|
||||
|
||||
@deprecated("use stop() instead", "1.1")
|
||||
def unload = stop
|
||||
|
||||
/**
|
||||
* Starts this CamelService.
|
||||
*/
|
||||
def start: CamelService = {
|
||||
// Only init and start if not already done by system
|
||||
if (!CamelContextManager.initialized) CamelContextManager.init
|
||||
if (!CamelContextManager.started) CamelContextManager.start
|
||||
|
||||
registerPublishRequestor
|
||||
|
||||
// send registration events for all (un)typed actors that have been registered in the past.
|
||||
for (event ← PublishRequestor.pastActorRegisteredEvents) publishRequestor ! event
|
||||
|
||||
// init publishRequestor so that buffered and future events are delivered to consumerPublisher
|
||||
publishRequestor ! InitPublishRequestor(consumerPublisher)
|
||||
|
||||
for (tc ← TypedCamelModule.typedCamelObject) tc.onCamelServiceStart(this)
|
||||
|
||||
// Register this instance as current CamelService and return it
|
||||
CamelServiceManager.register(this)
|
||||
CamelServiceManager.mandatoryService
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops this CamelService.
|
||||
*/
|
||||
def stop = {
|
||||
// Unregister this instance as current CamelService
|
||||
CamelServiceManager.unregister(this)
|
||||
|
||||
for (tc ← TypedCamelModule.typedCamelObject) tc.onCamelServiceStop(this)
|
||||
|
||||
// Remove related listeners from registry
|
||||
unregisterPublishRequestor
|
||||
|
||||
// Stop related services
|
||||
consumerPublisher.stop
|
||||
activationTracker.stop
|
||||
CamelContextManager.stop
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for an expected number (<code>count</code>) of consumer actor endpoints to be activated
|
||||
* during execution of <code>f</code>. The wait-timeout is by default 10 seconds. Other timeout
|
||||
* values can be set via the <code>timeout</code> and <code>timeUnit</code> parameters.
|
||||
*/
|
||||
def awaitEndpointActivation(count: Int, timeout: Long = 10, timeUnit: TimeUnit = TimeUnit.SECONDS)(f: ⇒ Unit): Boolean = {
|
||||
val activation = expectEndpointActivationCount(count)
|
||||
f; activation.await(timeout, timeUnit)
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for an expected number (<code>count</code>) of consumer actor endpoints to be de-activated
|
||||
* during execution of <code>f</code>. The wait-timeout is by default 10 seconds. Other timeout
|
||||
* values can be set via the <code>timeout</code> and <code>timeUnit</code>
|
||||
* parameters.
|
||||
*/
|
||||
def awaitEndpointDeactivation(count: Int, timeout: Long = 10, timeUnit: TimeUnit = TimeUnit.SECONDS)(f: ⇒ Unit): Boolean = {
|
||||
val activation = expectEndpointDeactivationCount(count)
|
||||
f; activation.await(timeout, timeUnit)
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for an expected number (<code>count</code>) of consumer actor endpoints to be activated
|
||||
* during execution of <code>p</code>. The wait timeout is 10 seconds.
|
||||
* <p>
|
||||
* Java API
|
||||
*/
|
||||
def awaitEndpointActivation(count: Int, p: SideEffect): Boolean = {
|
||||
awaitEndpointActivation(count, 10, TimeUnit.SECONDS, p)
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for an expected number (<code>count</code>) of consumer actor endpoints to be activated
|
||||
* during execution of <code>p</code>. Timeout values can be set via the
|
||||
* <code>timeout</code> and <code>timeUnit</code> parameters.
|
||||
* <p>
|
||||
* Java API
|
||||
*/
|
||||
def awaitEndpointActivation(count: Int, timeout: Long, timeUnit: TimeUnit, p: SideEffect): Boolean = {
|
||||
awaitEndpointActivation(count, timeout, timeUnit) { p.apply }
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for an expected number (<code>count</code>) of consumer actor endpoints to be de-activated
|
||||
* during execution of <code>p</code>. The wait timeout is 10 seconds.
|
||||
* <p>
|
||||
* Java API
|
||||
*/
|
||||
def awaitEndpointDeactivation(count: Int, p: SideEffect): Boolean = {
|
||||
awaitEndpointDeactivation(count, 10, TimeUnit.SECONDS, p)
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for an expected number (<code>count</code>) of consumer actor endpoints to be de-activated
|
||||
* during execution of <code>p</code>. Timeout values can be set via the
|
||||
* <code>timeout</code> and <code>timeUnit</code> parameters.
|
||||
* <p>
|
||||
* Java API
|
||||
*/
|
||||
def awaitEndpointDeactivation(count: Int, timeout: Long, timeUnit: TimeUnit, p: SideEffect): Boolean = {
|
||||
awaitEndpointDeactivation(count, timeout, timeUnit) { p.apply }
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an expectation on the number of upcoming endpoint activations and returns
|
||||
* a CountDownLatch that can be used to wait for the activations to occur. Endpoint
|
||||
* activations that occurred in the past are not considered.
|
||||
*/
|
||||
private def expectEndpointActivationCount(count: Int): CountDownLatch =
|
||||
Await.result((activationTracker ? SetExpectedActivationCount(count)).mapTo[CountDownLatch], 3 seconds)
|
||||
|
||||
/**
|
||||
* Sets an expectation on the number of upcoming endpoint de-activations and returns
|
||||
* a CountDownLatch that can be used to wait for the de-activations to occur. Endpoint
|
||||
* de-activations that occurred in the past are not considered.
|
||||
*/
|
||||
private def expectEndpointDeactivationCount(count: Int): CountDownLatch =
|
||||
Await.result((activationTracker ? SetExpectedDeactivationCount(count)).mapTo[CountDownLatch], 3 seconds)
|
||||
|
||||
private[camel] def registerPublishRequestor: Unit =
|
||||
Actor.registry.addListener(publishRequestor)
|
||||
|
||||
private[camel] def unregisterPublishRequestor: Unit =
|
||||
Actor.registry.removeListener(publishRequestor)
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages a CamelService (the 'current' CamelService).
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
object CamelServiceManager {
|
||||
|
||||
/**
|
||||
* The current CamelService which is defined when a CamelService has been started.
|
||||
*/
|
||||
private var _current: Option[CamelService] = None
|
||||
|
||||
/**
|
||||
* Starts a new CamelService, makes it the current CamelService and returns it.
|
||||
*
|
||||
* @see CamelService#start
|
||||
* @see CamelService#onLoad
|
||||
*/
|
||||
def startCamelService = CamelServiceFactory.createCamelService.start
|
||||
|
||||
/**
|
||||
* Stops the current CamelService (if defined).
|
||||
*
|
||||
* @see CamelService#stop
|
||||
* @see CamelService#onUnload
|
||||
*/
|
||||
def stopCamelService = for (s ← service) s.stop
|
||||
|
||||
/**
|
||||
* Returns <code>Some(CamelService)</code> if this <code>CamelService</code>
|
||||
* has been started, <code>None</code> otherwise.
|
||||
*/
|
||||
def service = _current
|
||||
|
||||
/**
|
||||
* Returns the current <code>CamelService</code> if <code>CamelService</code>
|
||||
* has been started, otherwise throws an <code>IllegalStateException</code>.
|
||||
* <p>
|
||||
* Java API
|
||||
*/
|
||||
def getService: JOption[CamelService] = CamelServiceManager.service
|
||||
|
||||
/**
|
||||
* Returns <code>Some(CamelService)</code> (containing the current CamelService)
|
||||
* if this <code>CamelService</code>has been started, <code>None</code> otherwise.
|
||||
*/
|
||||
def mandatoryService =
|
||||
if (_current.isDefined) _current.get
|
||||
else throw new IllegalStateException("co current CamelService")
|
||||
|
||||
/**
|
||||
* Returns <code>Some(CamelService)</code> (containing the current CamelService)
|
||||
* if this <code>CamelService</code>has been started, <code>None</code> otherwise.
|
||||
* <p>
|
||||
* Java API
|
||||
*/
|
||||
def getMandatoryService = mandatoryService
|
||||
|
||||
private[camel] def register(service: CamelService) =
|
||||
if (_current.isDefined) throw new IllegalStateException("current CamelService already registered")
|
||||
else _current = Some(service)
|
||||
|
||||
private[camel] def unregister(service: CamelService) =
|
||||
if (_current == Some(service)) _current = None
|
||||
else throw new IllegalStateException("only current CamelService can be unregistered")
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
object CamelServiceFactory {
|
||||
/**
|
||||
* Creates a new CamelService instance.
|
||||
*/
|
||||
def createCamelService: CamelService = new CamelService {}
|
||||
|
||||
/**
|
||||
* Creates a new CamelService instance and initializes it with the given CamelContext.
|
||||
*/
|
||||
def createCamelService(camelContext: CamelContext): CamelService = {
|
||||
CamelContextManager.init(camelContext)
|
||||
createCamelService
|
||||
}
|
||||
}
|
||||
|
|
@ -1,144 +1,67 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2010 Typesafe Inc. <http://www.typesafe.com>
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camel
|
||||
|
||||
import internal.component.DurationTypeConverter
|
||||
import org.apache.camel.model.{ RouteDefinition, ProcessorDefinition }
|
||||
|
||||
import akka.actor._
|
||||
import akka.util.Duration
|
||||
import akka.util.duration._
|
||||
|
||||
/**
|
||||
* Mixed in by Actor implementations that consume message from Camel endpoints.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
trait Consumer { this: Actor ⇒
|
||||
import RouteDefinitionHandler._
|
||||
trait Consumer extends Actor with ConsumerConfig {
|
||||
|
||||
/**
|
||||
* The default route definition handler is the identity function
|
||||
*/
|
||||
private[camel] var routeDefinitionHandler: RouteDefinitionHandler = identity
|
||||
|
||||
/**
|
||||
* Returns the Camel endpoint URI to consume messages from.
|
||||
*/
|
||||
def endpointUri: String
|
||||
|
||||
protected[this] implicit def camel = CamelExtension(context.system)
|
||||
protected[this] implicit def camelContext = camel.context
|
||||
|
||||
camel.registerConsumer(endpointUri, this, activationTimeout)
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether two-way communications between an endpoint and this consumer actor
|
||||
* should be done in blocking or non-blocking mode (default is non-blocking). This method
|
||||
* doesn't have any effect on one-way communications (they'll never block).
|
||||
* For internal use only.
|
||||
*/
|
||||
def blocking = false
|
||||
private[camel] object DefaultConsumerConfig extends ConsumerConfig
|
||||
|
||||
trait ConsumerConfig {
|
||||
|
||||
/**
|
||||
* How long the actor should wait for activation before it fails.
|
||||
*/
|
||||
def activationTimeout: Duration = 10 seconds
|
||||
|
||||
/**
|
||||
* When endpoint is out-capable (can produce responses) replyTimeout is the maximum time
|
||||
* the endpoint can take to send the response before the message exchange fails. It defaults to 1 minute.
|
||||
* This setting is used for out-capable, in-only, manually acknowledged communication.
|
||||
* When the blocking is set to Blocking replyTimeout is ignored.
|
||||
*/
|
||||
def replyTimeout: Duration = 1 minute
|
||||
|
||||
/**
|
||||
* Determines whether one-way communications between an endpoint and this consumer actor
|
||||
* should be auto-acknowledged or system-acknowledged.
|
||||
* should be auto-acknowledged or application-acknowledged.
|
||||
* This flag has only effect when exchange is in-only.
|
||||
*/
|
||||
def autoack = true
|
||||
def autoack: Boolean = true
|
||||
|
||||
/**
|
||||
* Sets the route definition handler for creating a custom route to this consumer instance.
|
||||
* The route definition handler for creating a custom route to this consumer instance.
|
||||
*/
|
||||
def onRouteDefinition(h: RouteDefinition ⇒ ProcessorDefinition[_]): Unit = onRouteDefinition(from(h))
|
||||
//FIXME: write a test confirming onRouteDefinition gets called
|
||||
def onRouteDefinition(rd: RouteDefinition): ProcessorDefinition[_] = rd
|
||||
|
||||
/**
|
||||
* Sets the route definition handler for creating a custom route to this consumer instance.
|
||||
* <p>
|
||||
* Java API.
|
||||
* For internal use only. Converts this ConsumerConfig to camel URI parameters
|
||||
* @return
|
||||
*/
|
||||
def onRouteDefinition(h: RouteDefinitionHandler): Unit = routeDefinitionHandler = h
|
||||
}
|
||||
|
||||
/**
|
||||
* Java-friendly Consumer.
|
||||
*
|
||||
* Subclass this abstract class to create an MDB-style untyped consumer actor. This
|
||||
* class is meant to be used from Java.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
abstract class UntypedConsumerActor extends UntypedActor with Consumer {
|
||||
final override def endpointUri = getEndpointUri
|
||||
final override def blocking = isBlocking
|
||||
final override def autoack = isAutoack
|
||||
|
||||
/**
|
||||
* Returns the Camel endpoint URI to consume messages from.
|
||||
*/
|
||||
def getEndpointUri(): String
|
||||
|
||||
/**
|
||||
* Determines whether two-way communications between an endpoint and this consumer actor
|
||||
* should be done in blocking or non-blocking mode (default is non-blocking). This method
|
||||
* doesn't have any effect on one-way communications (they'll never block).
|
||||
*/
|
||||
def isBlocking() = super.blocking
|
||||
|
||||
/**
|
||||
* Determines whether one-way communications between an endpoint and this consumer actor
|
||||
* should be auto-acknowledged or system-acknowledged.
|
||||
*/
|
||||
def isAutoack() = super.autoack
|
||||
}
|
||||
|
||||
/**
|
||||
* A callback handler for route definitions to consumer actors.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
trait RouteDefinitionHandler {
|
||||
def onRouteDefinition(rd: RouteDefinition): ProcessorDefinition[_]
|
||||
}
|
||||
|
||||
/**
|
||||
* The identity route definition handler.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*
|
||||
*/
|
||||
class RouteDefinitionIdentity extends RouteDefinitionHandler {
|
||||
def onRouteDefinition(rd: RouteDefinition) = rd
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
object RouteDefinitionHandler {
|
||||
/**
|
||||
* Returns the identity route definition handler
|
||||
*/
|
||||
val identity = new RouteDefinitionIdentity
|
||||
|
||||
/**
|
||||
* Created a route definition handler from the given function.
|
||||
*/
|
||||
def from(f: RouteDefinition ⇒ ProcessorDefinition[_]) = new RouteDefinitionHandler {
|
||||
def onRouteDefinition(rd: RouteDefinition) = f(rd)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
private[camel] object Consumer {
|
||||
/**
|
||||
* Applies a function <code>f</code> to <code>actorRef</code> if <code>actorRef</code>
|
||||
* references a consumer actor. A valid reference to a consumer actor is a local actor
|
||||
* reference with a target actor that implements the <code>Consumer</code> trait. The
|
||||
* target <code>Consumer</code> instance is passed as argument to <code>f</code>. This
|
||||
* method returns <code>None</code> if <code>actorRef</code> is not a valid reference
|
||||
* to a consumer actor, <code>Some</code> contained the return value of <code>f</code>
|
||||
* otherwise.
|
||||
*/
|
||||
def withConsumer[T](actorRef: ActorRef)(f: Consumer ⇒ T): Option[T] = actorRef match {
|
||||
case l: LocalActorRef ⇒
|
||||
l.underlyingActorInstance match {
|
||||
case c: Consumer ⇒ Some(f(c))
|
||||
case _ ⇒ None
|
||||
}
|
||||
case _ ⇒ None
|
||||
}
|
||||
private[camel] def toCamelParameters: String = "autoack=%s&replyTimeout=%s" format (autoack, DurationTypeConverter.toString(replyTimeout))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,221 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2010 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
package akka.camel
|
||||
|
||||
import java.io.InputStream
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
import org.apache.camel.builder.RouteBuilder
|
||||
import org.apache.camel.model.RouteDefinition
|
||||
|
||||
import akka.actor._
|
||||
import akka.event.EventHandler
|
||||
|
||||
/**
|
||||
* Concrete publish requestor that requests publication of consumer actors on <code>ActorRegistered</code>
|
||||
* events and unpublication of consumer actors on <code>ActorUnregistered</code> events.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
private[camel] class ConsumerPublishRequestor extends PublishRequestor {
|
||||
def receiveActorRegistryEvent = {
|
||||
case ActorRegistered(_, actor) ⇒ for (event ← ConsumerActorRegistered.eventFor(actor)) deliverCurrentEvent(event)
|
||||
case ActorUnregistered(_, actor) ⇒ for (event ← ConsumerActorUnregistered.eventFor(actor)) deliverCurrentEvent(event)
|
||||
case _ ⇒ ()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Publishes consumer actors on <code>ConsumerActorRegistered</code> events and unpublishes
|
||||
* consumer actors on <code>ConsumerActorUnregistered</code> events. Publications are tracked
|
||||
* by sending an <code>activationTracker</code> an <code>EndpointActivated</code> event,
|
||||
* unpublications are tracked by sending an <code>EndpointActivated</code> event.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
private[camel] class ConsumerPublisher(activationTracker: ActorRef) extends Actor {
|
||||
import ConsumerPublisher._
|
||||
|
||||
def receive = {
|
||||
case r: ConsumerActorRegistered ⇒ {
|
||||
handleConsumerActorRegistered(r)
|
||||
activationTracker ! EndpointActivated
|
||||
}
|
||||
case u: ConsumerActorUnregistered ⇒ {
|
||||
handleConsumerActorUnregistered(u)
|
||||
activationTracker ! EndpointDeactivated
|
||||
}
|
||||
case _ ⇒ { /* ignore */ }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
private[camel] object ConsumerPublisher {
|
||||
/**
|
||||
* Creates a route to the registered consumer actor.
|
||||
*/
|
||||
def handleConsumerActorRegistered(event: ConsumerActorRegistered) {
|
||||
CamelContextManager.mandatoryContext.addRoutes(new ConsumerActorRouteBuilder(event))
|
||||
EventHandler notifyListeners EventHandler.Info(this, "published actor %s at endpoint %s" format (event.actorRef, event.endpointUri))
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the route to the already un-registered consumer actor.
|
||||
*/
|
||||
def handleConsumerActorUnregistered(event: ConsumerActorUnregistered) {
|
||||
CamelContextManager.mandatoryContext.stopRoute(event.uuid)
|
||||
EventHandler notifyListeners EventHandler.Info(this, "unpublished actor %s from endpoint %s" format (event.actorRef, event.endpointUri))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract builder of a route to a target which can be either an actor or an typed actor method.
|
||||
*
|
||||
* @param endpointUri endpoint URI of the consumer actor or typed actor method.
|
||||
* @param id unique route identifier.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
private[camel] abstract class ConsumerRouteBuilder(endpointUri: String, id: String) extends RouteBuilder {
|
||||
// TODO: make conversions configurable
|
||||
private val bodyConversions = Map(
|
||||
"file" -> classOf[InputStream])
|
||||
|
||||
def configure = {
|
||||
val schema = endpointUri take endpointUri.indexOf(":") // e.g. "http" from "http://whatever/..."
|
||||
val cnvopt = bodyConversions.get(schema)
|
||||
|
||||
onRouteDefinition(startRouteDefinition(cnvopt)).to(targetUri)
|
||||
}
|
||||
|
||||
protected def routeDefinitionHandler: RouteDefinitionHandler
|
||||
protected def targetUri: String
|
||||
|
||||
private def onRouteDefinition(rd: RouteDefinition) = routeDefinitionHandler.onRouteDefinition(rd)
|
||||
private def startRouteDefinition(bodyConversion: Option[Class[_]]): RouteDefinition = bodyConversion match {
|
||||
case Some(clazz) ⇒ from(endpointUri).routeId(id).convertBodyTo(clazz)
|
||||
case None ⇒ from(endpointUri).routeId(id)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder of a route to a consumer actor.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
private[camel] class ConsumerActorRouteBuilder(event: ConsumerActorRegistered) extends ConsumerRouteBuilder(event.endpointUri, event.uuid) {
|
||||
protected def routeDefinitionHandler: RouteDefinitionHandler = event.routeDefinitionHandler
|
||||
protected def targetUri = "actor:uuid:%s?blocking=%s&autoack=%s" format (event.uuid, event.blocking, event.autoack)
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks <code>EndpointActivated</code> and <code>EndpointDectivated</code> events. Used to wait for a
|
||||
* certain number of endpoints activations and de-activations to occur.
|
||||
*
|
||||
* @see SetExpectedActivationCount
|
||||
* @see SetExpectedDeactivationCount
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
private[camel] class ActivationTracker extends Actor {
|
||||
private var activationLatch = new CountDownLatch(0)
|
||||
private var deactivationLatch = new CountDownLatch(0)
|
||||
|
||||
def receive = {
|
||||
case SetExpectedActivationCount(num) ⇒ {
|
||||
activationLatch = new CountDownLatch(num)
|
||||
sender ! activationLatch
|
||||
}
|
||||
case SetExpectedDeactivationCount(num) ⇒ {
|
||||
deactivationLatch = new CountDownLatch(num)
|
||||
sender ! deactivationLatch
|
||||
}
|
||||
case EndpointActivated ⇒ activationLatch.countDown
|
||||
case EndpointDeactivated ⇒ deactivationLatch.countDown
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Command message that sets the number of expected endpoint activations on <code>ActivationTracker</code>.
|
||||
*/
|
||||
private[camel] case class SetExpectedActivationCount(num: Int)
|
||||
|
||||
/**
|
||||
* Command message that sets the number of expected endpoint de-activations on <code>ActivationTracker</code>.
|
||||
*/
|
||||
private[camel] case class SetExpectedDeactivationCount(num: Int)
|
||||
|
||||
/**
|
||||
* Event message indicating that a single endpoint has been activated.
|
||||
*/
|
||||
private[camel] case class EndpointActivated()
|
||||
|
||||
/**
|
||||
* Event message indicating that a single endpoint has been de-activated.
|
||||
*/
|
||||
private[camel] case class EndpointDeactivated()
|
||||
|
||||
/**
|
||||
* A consumer (un)registration event.
|
||||
*/
|
||||
private[camel] trait ConsumerEvent {
|
||||
val uuid: String
|
||||
}
|
||||
|
||||
/**
|
||||
* A consumer actor (un)registration event.
|
||||
*/
|
||||
private[camel] trait ConsumerActorEvent extends ConsumerEvent {
|
||||
val actorRef: ActorRef
|
||||
val actor: Consumer
|
||||
|
||||
val uuid = actorRef.uuid.toString
|
||||
val endpointUri = actor.endpointUri
|
||||
val blocking = actor.blocking
|
||||
val autoack = actor.autoack
|
||||
val routeDefinitionHandler = actor.routeDefinitionHandler
|
||||
}
|
||||
|
||||
/**
|
||||
* Event indicating that a consumer actor has been registered at the actor registry.
|
||||
*/
|
||||
private[camel] case class ConsumerActorRegistered(actorRef: ActorRef, actor: Consumer) extends ConsumerActorEvent
|
||||
|
||||
/**
|
||||
* Event indicating that a consumer actor has been unregistered from the actor registry.
|
||||
*/
|
||||
private[camel] case class ConsumerActorUnregistered(actorRef: ActorRef, actor: Consumer) extends ConsumerActorEvent
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
private[camel] object ConsumerActorRegistered {
|
||||
/**
|
||||
* Creates an ConsumerActorRegistered event message for a consumer actor or None if
|
||||
* <code>actorRef</code> is not a consumer actor.
|
||||
*/
|
||||
def eventFor(actorRef: ActorRef): Option[ConsumerActorRegistered] = {
|
||||
Consumer.withConsumer[ConsumerActorRegistered](actorRef) { actor ⇒
|
||||
ConsumerActorRegistered(actorRef, actor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
private[camel] object ConsumerActorUnregistered {
|
||||
/**
|
||||
* Creates an ConsumerActorUnregistered event message for a consumer actor or None if
|
||||
* <code>actorRef</code> is not a consumer actor.
|
||||
*/
|
||||
def eventFor(actorRef: ActorRef): Option[ConsumerActorUnregistered] = {
|
||||
Consumer.withConsumer[ConsumerActorUnregistered](actorRef) { actor ⇒
|
||||
ConsumerActorUnregistered(actorRef, actor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,385 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2010 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
package akka.camel
|
||||
|
||||
import java.util.{ Map ⇒ JMap, Set ⇒ JSet }
|
||||
|
||||
import scala.collection.JavaConversions._
|
||||
|
||||
import org.apache.camel.{ Exchange, Message ⇒ CamelMessage }
|
||||
import org.apache.camel.util.ExchangeHelper
|
||||
|
||||
import akka.japi.{ Function ⇒ JFunction }
|
||||
|
||||
/**
|
||||
* An immutable representation of a Camel message.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
case class Message(val body: Any, val headers: Map[String, Any] = Map.empty) {
|
||||
|
||||
/**
|
||||
* Creates a Message with given body and empty headers map.
|
||||
*/
|
||||
def this(body: Any) = this(body, Map.empty[String, Any])
|
||||
|
||||
/**
|
||||
* Creates a Message with given body and headers map. A copy of the headers map is made.
|
||||
* <p>
|
||||
* Java API
|
||||
*/
|
||||
def this(body: Any, headers: JMap[String, Any]) = this(body, headers.toMap)
|
||||
|
||||
/**
|
||||
* Returns the body of the message converted to the type <code>T</code>. Conversion is done
|
||||
* using Camel's type converter. The type converter is obtained from the CamelContext managed
|
||||
* by CamelContextManager. Applications have to ensure proper initialization of
|
||||
* CamelContextManager.
|
||||
*
|
||||
* @see CamelContextManager.
|
||||
*/
|
||||
def bodyAs[T](implicit m: Manifest[T]): T = getBodyAs(m.erasure.asInstanceOf[Class[T]])
|
||||
|
||||
/**
|
||||
* Returns the body of the message converted to the type as given by the <code>clazz</code>
|
||||
* parameter. Conversion is done using Camel's type converter. The type converter is obtained
|
||||
* from the CamelContext managed by CamelContextManager. Applications have to ensure proper
|
||||
* initialization of CamelContextManager.
|
||||
* <p>
|
||||
* Java API
|
||||
*
|
||||
* @see CamelContextManager.
|
||||
*/
|
||||
def getBodyAs[T](clazz: Class[T]): T =
|
||||
CamelContextManager.mandatoryContext.getTypeConverter.mandatoryConvertTo[T](clazz, body)
|
||||
|
||||
/**
|
||||
* Returns those headers from this message whose name is contained in <code>names</code>.
|
||||
*/
|
||||
def headers(names: Set[String]): Map[String, Any] = headers.filter(names contains _._1)
|
||||
|
||||
/**
|
||||
* Returns those headers from this message whose name is contained in <code>names</code>.
|
||||
* The returned headers map is backed up by an immutable headers map. Any attempt to modify
|
||||
* the returned map will throw an exception.
|
||||
* <p>
|
||||
* Java API
|
||||
*/
|
||||
def getHeaders(names: JSet[String]): JMap[String, Any] = headers.filter(names contains _._1)
|
||||
|
||||
/**
|
||||
* Returns all headers from this message. The returned headers map is backed up by this
|
||||
* message's immutable headers map. Any attempt to modify the returned map will throw an
|
||||
* exception.
|
||||
* <p>
|
||||
* Java API
|
||||
*/
|
||||
def getHeaders: JMap[String, Any] = headers
|
||||
|
||||
/**
|
||||
* Returns the header with given <code>name</code>. Throws <code>NoSuchElementException</code>
|
||||
* if the header doesn't exist.
|
||||
*/
|
||||
def header(name: String): Any = headers(name)
|
||||
|
||||
/**
|
||||
* Returns the header with given <code>name</code>. Throws <code>NoSuchElementException</code>
|
||||
* if the header doesn't exist.
|
||||
* <p>
|
||||
* Java API
|
||||
*/
|
||||
def getHeader(name: String): Any = header(name)
|
||||
|
||||
/**
|
||||
* Returns the header with given <code>name</code> converted to type <code>T</code>. Throws
|
||||
* <code>NoSuchElementException</code> if the header doesn't exist.
|
||||
*/
|
||||
def headerAs[T](name: String)(implicit m: Manifest[T]): T =
|
||||
getHeaderAs(name, m.erasure.asInstanceOf[Class[T]])
|
||||
|
||||
/**
|
||||
* Returns the header with given <code>name</code> converted to type as given by the <code>clazz</code>
|
||||
* parameter. Throws <code>NoSuchElementException</code> if the header doesn't exist.
|
||||
* <p>
|
||||
* Java API
|
||||
*/
|
||||
def getHeaderAs[T](name: String, clazz: Class[T]): T =
|
||||
CamelContextManager.mandatoryContext.getTypeConverter.mandatoryConvertTo[T](clazz, header(name))
|
||||
|
||||
/**
|
||||
* Creates a Message with a transformed body using a <code>transformer</code> function.
|
||||
*/
|
||||
def transformBody[A](transformer: A ⇒ Any): Message = setBody(transformer(body.asInstanceOf[A]))
|
||||
|
||||
/**
|
||||
* Creates a Message with a transformed body using a <code>transformer</code> function.
|
||||
* <p>
|
||||
* Java API
|
||||
*/
|
||||
def transformBody[A](transformer: JFunction[A, Any]): Message = setBody(transformer(body.asInstanceOf[A]))
|
||||
|
||||
/**
|
||||
* Creates a Message with current <code>body</code> converted to type <code>T</code>.
|
||||
*/
|
||||
def setBodyAs[T](implicit m: Manifest[T]): Message = setBodyAs(m.erasure.asInstanceOf[Class[T]])
|
||||
|
||||
/**
|
||||
* Creates a Message with current <code>body</code> converted to type <code>clazz</code>.
|
||||
* <p>
|
||||
* Java API
|
||||
*/
|
||||
def setBodyAs[T](clazz: Class[T]): Message = setBody(getBodyAs(clazz))
|
||||
|
||||
/**
|
||||
* Creates a Message with a given <code>body</code>.
|
||||
*/
|
||||
def setBody(body: Any) = new Message(body, this.headers)
|
||||
|
||||
/**
|
||||
* Creates a new Message with given <code>headers</code>.
|
||||
*/
|
||||
def setHeaders(headers: Map[String, Any]): Message = copy(this.body, headers)
|
||||
|
||||
/**
|
||||
* Creates a new Message with given <code>headers</code>. A copy of the headers map is made.
|
||||
* <p>
|
||||
* Java API
|
||||
*/
|
||||
def setHeaders(headers: JMap[String, Any]): Message = setHeaders(headers.toMap)
|
||||
|
||||
/**
|
||||
* Creates a new Message with given <code>headers</code> added to the current headers.
|
||||
*/
|
||||
def addHeaders(headers: Map[String, Any]): Message = copy(this.body, this.headers ++ headers)
|
||||
|
||||
/**
|
||||
* Creates a new Message with given <code>headers</code> added to the current headers.
|
||||
* A copy of the headers map is made.
|
||||
* <p>
|
||||
* Java API
|
||||
*/
|
||||
def addHeaders(headers: JMap[String, Any]): Message = addHeaders(headers.toMap)
|
||||
|
||||
/**
|
||||
* Creates a new Message with the given <code>header</code> added to the current headers.
|
||||
*/
|
||||
def addHeader(header: (String, Any)): Message = copy(this.body, this.headers + header)
|
||||
|
||||
/**
|
||||
* Creates a new Message with the given header, represented by <code>name</code> and
|
||||
* <code>value</code> added to the existing headers.
|
||||
* <p>
|
||||
* Java API
|
||||
*/
|
||||
def addHeader(name: String, value: Any): Message = addHeader((name, value))
|
||||
|
||||
/**
|
||||
* Creates a new Message where the header with given <code>headerName</code> is removed from
|
||||
* the existing headers.
|
||||
*/
|
||||
def removeHeader(headerName: String) = copy(this.body, this.headers - headerName)
|
||||
}
|
||||
|
||||
/**
|
||||
* Companion object of Message class.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
object Message {
|
||||
|
||||
/**
|
||||
* Message header to correlate request with response messages. Applications that send
|
||||
* messages to a Producer actor may want to set this header on the request message
|
||||
* so that it can be correlated with an asynchronous response. Messages send to Consumer
|
||||
* actors have this header already set.
|
||||
*/
|
||||
val MessageExchangeId = "MessageExchangeId".intern
|
||||
|
||||
/**
|
||||
* Creates a canonical form of the given message <code>msg</code>. If <code>msg</code> of type
|
||||
* Message then <code>msg</code> is returned, otherwise <code>msg</code> is set as body of a
|
||||
* newly created Message object.
|
||||
*/
|
||||
def canonicalize(msg: Any) = msg match {
|
||||
case mobj: Message ⇒ mobj
|
||||
case body ⇒ new Message(body)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Positive acknowledgement message (used for system-acknowledged message receipts).
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
case object Ack {
|
||||
/** Java API to get the Ack singleton */
|
||||
def ack = this
|
||||
}
|
||||
|
||||
/**
|
||||
* An immutable representation of a failed Camel exchange. It contains the failure cause
|
||||
* obtained from Exchange.getException and the headers from either the Exchange.getIn
|
||||
* message or Exchange.getOut message, depending on the exchange pattern.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
case class Failure(val cause: Throwable, val headers: Map[String, Any] = Map.empty) {
|
||||
|
||||
/**
|
||||
* Creates a Failure with cause body and empty headers map.
|
||||
*/
|
||||
def this(cause: Throwable) = this(cause, Map.empty[String, Any])
|
||||
|
||||
/**
|
||||
* Creates a Failure with given cause and headers map. A copy of the headers map is made.
|
||||
* <p>
|
||||
* Java API
|
||||
*/
|
||||
def this(cause: Throwable, headers: JMap[String, Any]) = this(cause, headers.toMap)
|
||||
|
||||
/**
|
||||
* Returns the cause of this Failure.
|
||||
* <p>
|
||||
* Java API.
|
||||
*/
|
||||
def getCause = cause
|
||||
|
||||
/**
|
||||
* Returns all headers from this failure message. The returned headers map is backed up by
|
||||
* this message's immutable headers map. Any attempt to modify the returned map will throw
|
||||
* an exception.
|
||||
* <p>
|
||||
* Java API
|
||||
*/
|
||||
def getHeaders: JMap[String, Any] = headers
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapter for converting an org.apache.camel.Exchange to and from Message and Failure objects.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
class CamelExchangeAdapter(exchange: Exchange) {
|
||||
import CamelMessageConversion.toMessageAdapter
|
||||
|
||||
/**
|
||||
* Sets Exchange.getIn from the given Message object.
|
||||
*/
|
||||
def fromRequestMessage(msg: Message): Exchange = { requestMessage.fromMessage(msg); exchange }
|
||||
|
||||
/**
|
||||
* Depending on the exchange pattern, sets Exchange.getIn or Exchange.getOut from the given
|
||||
* Message object. If the exchange is out-capable then the Exchange.getOut is set, otherwise
|
||||
* Exchange.getIn.
|
||||
*/
|
||||
def fromResponseMessage(msg: Message): Exchange = { responseMessage.fromMessage(msg); exchange }
|
||||
|
||||
/**
|
||||
* Sets Exchange.getException from the given Failure message. Headers of the Failure message
|
||||
* are ignored.
|
||||
*/
|
||||
def fromFailureMessage(msg: Failure): Exchange = { exchange.setException(msg.cause); exchange }
|
||||
|
||||
/**
|
||||
* Creates a Message object from Exchange.getIn.
|
||||
*/
|
||||
def toRequestMessage: Message = toRequestMessage(Map.empty)
|
||||
|
||||
/**
|
||||
* Depending on the exchange pattern, creates a Message object from Exchange.getIn or Exchange.getOut.
|
||||
* If the exchange is out-capable then the Exchange.getOut is set, otherwise Exchange.getIn.
|
||||
*/
|
||||
def toResponseMessage: Message = toResponseMessage(Map.empty)
|
||||
|
||||
/**
|
||||
* Creates a Failure object from the adapted Exchange.
|
||||
*
|
||||
* @see Failure
|
||||
*/
|
||||
def toFailureMessage: Failure = toFailureMessage(Map.empty)
|
||||
|
||||
/**
|
||||
* Creates a Message object from Exchange.getIn.
|
||||
*
|
||||
* @param headers additional headers to set on the created Message in addition to those
|
||||
* in the Camel message.
|
||||
*/
|
||||
def toRequestMessage(headers: Map[String, Any]): Message = requestMessage.toMessage(headers)
|
||||
|
||||
/**
|
||||
* Depending on the exchange pattern, creates a Message object from Exchange.getIn or Exchange.getOut.
|
||||
* If the exchange is out-capable then the Exchange.getOut is set, otherwise Exchange.getIn.
|
||||
*
|
||||
* @param headers additional headers to set on the created Message in addition to those
|
||||
* in the Camel message.
|
||||
*/
|
||||
def toResponseMessage(headers: Map[String, Any]): Message = responseMessage.toMessage(headers)
|
||||
|
||||
/**
|
||||
* Creates a Failure object from the adapted Exchange.
|
||||
*
|
||||
* @param headers additional headers to set on the created Message in addition to those
|
||||
* in the Camel message.
|
||||
*
|
||||
* @see Failure
|
||||
*/
|
||||
def toFailureMessage(headers: Map[String, Any]): Failure =
|
||||
Failure(exchange.getException, headers ++ responseMessage.toMessage.headers)
|
||||
|
||||
private def requestMessage = exchange.getIn
|
||||
|
||||
private def responseMessage = ExchangeHelper.getResultMessage(exchange)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapter for converting an org.apache.camel.Message to and from Message objects.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
class CamelMessageAdapter(val cm: CamelMessage) {
|
||||
/**
|
||||
* Set the adapted Camel message from the given Message object.
|
||||
*/
|
||||
def fromMessage(m: Message): CamelMessage = {
|
||||
cm.setBody(m.body)
|
||||
for (h ← m.headers) cm.getHeaders.put(h._1, h._2.asInstanceOf[AnyRef])
|
||||
cm
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Message object from the adapted Camel message.
|
||||
*/
|
||||
def toMessage: Message = toMessage(Map.empty)
|
||||
|
||||
/**
|
||||
* Creates a new Message object from the adapted Camel message.
|
||||
*
|
||||
* @param headers additional headers to set on the created Message in addition to those
|
||||
* in the Camel message.
|
||||
*/
|
||||
def toMessage(headers: Map[String, Any]): Message = Message(cm.getBody, cmHeaders(headers, cm))
|
||||
|
||||
private def cmHeaders(headers: Map[String, Any], cm: CamelMessage) = headers ++ cm.getHeaders
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines conversion methods to CamelExchangeAdapter and CamelMessageAdapter.
|
||||
* Imported by applications that implicitly want to use conversion methods of
|
||||
* CamelExchangeAdapter and CamelMessageAdapter.
|
||||
*/
|
||||
object CamelMessageConversion {
|
||||
|
||||
/**
|
||||
* Creates an CamelExchangeAdapter for the given Camel exchange.
|
||||
*/
|
||||
implicit def toExchangeAdapter(ce: Exchange): CamelExchangeAdapter =
|
||||
new CamelExchangeAdapter(ce)
|
||||
|
||||
/**
|
||||
* Creates an CamelMessageAdapter for the given Camel message.
|
||||
*/
|
||||
implicit def toMessageAdapter(cm: CamelMessage): CamelMessageAdapter =
|
||||
new CamelMessageAdapter(cm)
|
||||
}
|
||||
|
|
@ -1,16 +1,12 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2010 Typesafe Inc. <http://www.typesafe.com>
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camel
|
||||
|
||||
import CamelMessageConversion.toExchangeAdapter
|
||||
|
||||
import org.apache.camel._
|
||||
import org.apache.camel.processor.SendProcessor
|
||||
|
||||
import akka.actor.{ Actor, ActorRef, UntypedActor }
|
||||
import akka.dispatch.ActorPromise
|
||||
import akka.actor.Actor
|
||||
import internal.CamelExchangeAdapter
|
||||
import org.apache.camel.{ Exchange, ExchangePattern, AsyncCallback }
|
||||
|
||||
/**
|
||||
* Support trait for producing messages to Camel endpoints.
|
||||
|
|
@ -18,22 +14,14 @@ import akka.dispatch.ActorPromise
|
|||
* @author Martin Krasser
|
||||
*/
|
||||
trait ProducerSupport { this: Actor ⇒
|
||||
protected[this] implicit def camel = CamelExtension(context.system)
|
||||
|
||||
protected[this] lazy val (endpoint, processor) = camel.registerProducer(self, endpointUri)
|
||||
|
||||
/**
|
||||
* Message headers to copy by default from request message to response-message.
|
||||
* CamelMessage headers to copy by default from request message to response-message.
|
||||
*/
|
||||
private val headersToCopyDefault = Set(Message.MessageExchangeId)
|
||||
|
||||
/**
|
||||
* <code>Endpoint</code> object resolved from the current CamelContext with
|
||||
* <code>endpointUri</code>.
|
||||
*/
|
||||
private lazy val endpoint = CamelContextManager.mandatoryContext.getEndpoint(endpointUri)
|
||||
|
||||
/**
|
||||
* <code>SendProcessor</code> for producing messages to <code>endpoint</code>.
|
||||
*/
|
||||
private lazy val processor = createSendProcessor
|
||||
private val headersToCopyDefault = Set(CamelMessage.MessageExchangeId)
|
||||
|
||||
/**
|
||||
* If set to false (default), this producer expects a response message from the Camel endpoint.
|
||||
|
|
@ -49,33 +37,11 @@ trait ProducerSupport { this: Actor ⇒
|
|||
|
||||
/**
|
||||
* Returns the names of message headers to copy from a request message to a response message.
|
||||
* By default only the Message.MessageExchangeId is copied. Applications may override this to
|
||||
* define an system-specific set of message headers to copy.
|
||||
* By default only the CamelMessage.MessageExchangeId is copied. Applications may override this to
|
||||
* define an application-specific set of message headers to copy.
|
||||
*/
|
||||
def headersToCopy: Set[String] = headersToCopyDefault
|
||||
|
||||
/**
|
||||
* Default implementation of <code>Actor.preRestart</code> for freeing resources needed
|
||||
* to actually send messages to <code>endpointUri</code>.
|
||||
*/
|
||||
override def preRestart(reason: Throwable, msg: Option[Any]) {
|
||||
try { preRestartProducer(reason) } finally { processor.stop }
|
||||
}
|
||||
|
||||
/**
|
||||
* Does nothing by default. Can be overridden by concrete producers for implementing a
|
||||
* pre-restart callback handler.
|
||||
*/
|
||||
def preRestartProducer(reason: Throwable) {}
|
||||
|
||||
/**
|
||||
* Default implementation of <code>Actor.postStop</code> for freeing resources needed
|
||||
* to actually send messages to <code>endpointUri</code>.
|
||||
*/
|
||||
override def postStop {
|
||||
processor.stop
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates a message exchange of given <code>pattern</code> with the endpoint specified by
|
||||
* <code>endpointUri</code>. The in-message of the initiated exchange is the canonical form
|
||||
|
|
@ -83,44 +49,29 @@ trait ProducerSupport { this: Actor ⇒
|
|||
* as argument to <code>receiveAfterProduce</code>. If the response is received synchronously from
|
||||
* the endpoint then <code>receiveAfterProduce</code> is called synchronously as well. If the
|
||||
* response is received asynchronously, the <code>receiveAfterProduce</code> is called
|
||||
* asynchronously. This is done by wrapping the response, adding it to this producers
|
||||
* mailbox, unwrapping it and calling <code>receiveAfterProduce</code>. The original
|
||||
* sender and senderFuture are thereby preserved.
|
||||
* asynchronously. The original
|
||||
* sender and senderFuture are preserved.
|
||||
*
|
||||
* @see Message#canonicalize(Any)
|
||||
* @see CamelMessage#canonicalize(Any)
|
||||
*
|
||||
* @param msg message to produce
|
||||
* @param pattern exchange pattern
|
||||
*/
|
||||
protected def produce(msg: Any, pattern: ExchangePattern) {
|
||||
val cmsg = Message.canonicalize(msg)
|
||||
val exchange = createExchange(pattern).fromRequestMessage(cmsg)
|
||||
protected def produce(msg: Any, pattern: ExchangePattern): Unit = {
|
||||
implicit def toExchangeAdapter(exchange: Exchange): CamelExchangeAdapter = new CamelExchangeAdapter(exchange)
|
||||
|
||||
val cmsg = CamelMessage.canonicalize(msg)
|
||||
val exchange = endpoint.createExchange(pattern)
|
||||
exchange.setRequest(cmsg)
|
||||
processor.process(exchange, new AsyncCallback {
|
||||
val producer = self
|
||||
// Need copies of sender reference here since the callback could be done
|
||||
// later by another thread.
|
||||
val replyChannel = sender
|
||||
|
||||
def done(doneSync: Boolean) {
|
||||
(doneSync, exchange.isFailed) match {
|
||||
case (true, true) ⇒ dispatchSync(exchange.toFailureMessage(cmsg.headers(headersToCopy)))
|
||||
case (true, false) ⇒ dispatchSync(exchange.toResponseMessage(cmsg.headers(headersToCopy)))
|
||||
case (false, true) ⇒ dispatchAsync(FailureResult(exchange.toFailureMessage(cmsg.headers(headersToCopy))))
|
||||
case (false, false) ⇒ dispatchAsync(MessageResult(exchange.toResponseMessage(cmsg.headers(headersToCopy))))
|
||||
}
|
||||
}
|
||||
|
||||
private def dispatchSync(result: Any) =
|
||||
receiveAfterProduce(result)
|
||||
|
||||
private def dispatchAsync(result: Any) = {
|
||||
replyChannel match {
|
||||
case _: ActorPromise ⇒
|
||||
producer.postMessageToMailboxAndCreateFutureResultWithTimeout(result, producer.timeout, replyChannel)
|
||||
case _ ⇒
|
||||
producer.postMessageToMailbox(result, replyChannel)
|
||||
}
|
||||
}
|
||||
val originalSender = sender
|
||||
// Ignoring doneSync, sending back async uniformly.
|
||||
def done(doneSync: Boolean): Unit = producer.tell(
|
||||
if (exchange.isFailed) FailureResult(exchange.toFailureMessage(cmsg.headers(headersToCopy)))
|
||||
else MessageResult(exchange.toResponseMessage(cmsg.headers(headersToCopy))), originalSender)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -162,20 +113,6 @@ trait ProducerSupport { this: Actor ⇒
|
|||
case msg ⇒ if (!oneway) sender ! msg
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Exchange of given <code>pattern</code> from the endpoint specified by
|
||||
* <code>endpointUri</code>.
|
||||
*/
|
||||
private def createExchange(pattern: ExchangePattern): Exchange = endpoint.createExchange(pattern)
|
||||
|
||||
/**
|
||||
* Creates a new <code>SendProcessor</code> for <code>endpoint</code>.
|
||||
*/
|
||||
private def createSendProcessor = {
|
||||
val sendProcessor = new SendProcessor(endpoint)
|
||||
sendProcessor.start()
|
||||
sendProcessor
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -191,66 +128,14 @@ trait Producer extends ProducerSupport { this: Actor ⇒
|
|||
}
|
||||
|
||||
/**
|
||||
* Subclass this abstract class to create an untyped producer actor. This class is meant to be used from Java.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
abstract class UntypedProducerActor extends UntypedActor with ProducerSupport {
|
||||
final override def endpointUri = getEndpointUri
|
||||
final override def oneway = isOneway
|
||||
|
||||
final override def receiveBeforeProduce = {
|
||||
case msg ⇒ onReceiveBeforeProduce(msg)
|
||||
}
|
||||
|
||||
final override def receiveAfterProduce = {
|
||||
case msg ⇒ onReceiveAfterProduce(msg)
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation of UntypedActor.onReceive
|
||||
*/
|
||||
def onReceive(message: Any) = produce(message)
|
||||
|
||||
/**
|
||||
* Returns the Camel endpoint URI to produce messages to.
|
||||
*/
|
||||
def getEndpointUri(): String
|
||||
|
||||
/**
|
||||
* If set to false (default), this producer expects a response message from the Camel endpoint.
|
||||
* If set to true, this producer communicates with the Camel endpoint with an in-only message
|
||||
* exchange pattern (fire and forget).
|
||||
*/
|
||||
def isOneway() = super.oneway
|
||||
|
||||
/**
|
||||
* Called before the message is sent to the endpoint specified by <code>getEndpointUri</code>. The original
|
||||
* message is passed as argument. By default, this method simply returns the argument but may be overridden
|
||||
* by subclasses.
|
||||
*/
|
||||
@throws(classOf[Exception])
|
||||
def onReceiveBeforeProduce(message: Any): Any = super.receiveBeforeProduce(message)
|
||||
|
||||
/**
|
||||
* Called after a response was received from the endpoint specified by <code>endpointUri</code>. The
|
||||
* response is passed as argument. By default, this method sends the response back to the original sender
|
||||
* if <code>oneway</code> is <code>false</code>. If <code>oneway</code> is <code>true</code>, nothing is
|
||||
* done. This method may be overridden by subclasses (e.g. to forward responses to another actor).
|
||||
*/
|
||||
@throws(classOf[Exception])
|
||||
def onReceiveAfterProduce(message: Any): Unit = super.receiveAfterProduce(message)
|
||||
}
|
||||
private case class MessageResult(message: CamelMessage)
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
private[camel] case class MessageResult(message: Message)
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
private[camel] case class FailureResult(failure: Failure)
|
||||
private case class FailureResult(failure: Failure)
|
||||
|
||||
/**
|
||||
* A one-way producer.
|
||||
|
|
|
|||
|
|
@ -1,64 +0,0 @@
|
|||
package akka.camel
|
||||
|
||||
import akka.actor._
|
||||
|
||||
/**
|
||||
* Base class for concrete (un)publish requestors. Subclasses are responsible for requesting
|
||||
* (un)publication of consumer actors by calling <code>deliverCurrentEvent</code>.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
private[camel] abstract class PublishRequestor extends Actor {
|
||||
private val events = collection.mutable.Set[ConsumerEvent]()
|
||||
private var publisher: Option[ActorRef] = None
|
||||
|
||||
def receiveActorRegistryEvent: Receive
|
||||
|
||||
/**
|
||||
* Accepts
|
||||
* <ul>
|
||||
* <li><code>InitPublishRequestor</code> messages to configure a publisher for this requestor.</li>
|
||||
* <li><code>ActorRegistryEvent</code> messages to be handled <code>receiveActorRegistryEvent</code>
|
||||
* implementators</li>.
|
||||
* </ul>
|
||||
* Other messages are simply ignored. Calls to <code>deliverCurrentEvent</code> prior to setting a
|
||||
* publisher are buffered. They will be sent after a publisher has been set.
|
||||
*/
|
||||
def receive = {
|
||||
case InitPublishRequestor(pub) ⇒ {
|
||||
publisher = Some(pub)
|
||||
deliverBufferedEvents
|
||||
}
|
||||
case e: ActorRegistryEvent ⇒ receiveActorRegistryEvent(e)
|
||||
case _ ⇒ { /* ignore */ }
|
||||
}
|
||||
|
||||
/**
|
||||
* Deliver the given <code>event</code> to <code>publisher</code> or buffer the event if
|
||||
* <code>publisher</code> is not defined yet.
|
||||
*/
|
||||
protected def deliverCurrentEvent(event: ConsumerEvent) {
|
||||
publisher match {
|
||||
case Some(pub) ⇒ pub ! event
|
||||
case None ⇒ events += event
|
||||
}
|
||||
}
|
||||
|
||||
private def deliverBufferedEvents {
|
||||
for (event ← events) deliverCurrentEvent(event)
|
||||
events.clear
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
private[camel] object PublishRequestor {
|
||||
def pastActorRegisteredEvents = for (actor ← Actor.registry.local.actors) yield ActorRegistered(actor.address, actor)
|
||||
}
|
||||
|
||||
/**
|
||||
* Command message to initialize a PublishRequestor to use <code>publisher</code>
|
||||
* for publishing consumer actors.
|
||||
*/
|
||||
private[camel] case class InitPublishRequestor(publisher: ActorRef)
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camel
|
||||
|
||||
import org.apache.camel.CamelContext
|
||||
import akka.event.EventHandler
|
||||
|
||||
import akka.util.ReflectiveAccess.getObjectFor
|
||||
|
||||
/**
|
||||
* Module for reflective access to akka-camel-typed.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
private[camel] object TypedCamelAccess {
|
||||
val loader = getClass.getClassLoader
|
||||
|
||||
object TypedCamelModule {
|
||||
|
||||
type TypedCamelObject = {
|
||||
def onCamelContextInit(context: CamelContext): Unit
|
||||
def onCamelServiceStart(service: CamelService): Unit
|
||||
def onCamelServiceStop(service: CamelService): Unit
|
||||
}
|
||||
|
||||
val typedCamelObject: Option[TypedCamelObject] =
|
||||
getObjectFor("akka.camel.TypedCamel$", loader) match {
|
||||
case Right(value) ⇒ Some(value)
|
||||
case Left(exception) ⇒
|
||||
EventHandler.debug(this, exception.toString)
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,302 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2010 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camel.component
|
||||
|
||||
import java.net.InetSocketAddress
|
||||
import java.util.{ Map ⇒ JMap }
|
||||
import java.util.concurrent.TimeoutException
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
import org.apache.camel._
|
||||
import org.apache.camel.impl.{ DefaultProducer, DefaultEndpoint, DefaultComponent }
|
||||
|
||||
import akka.actor._
|
||||
import akka.camel.{ Ack, Failure, Message }
|
||||
import akka.camel.CamelMessageConversion.toExchangeAdapter
|
||||
import scala.reflect.BeanProperty
|
||||
import akka.dispatch._
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
object ActorComponent {
|
||||
/**
|
||||
* Name of the message header containing the actor id or uuid.
|
||||
*/
|
||||
val ActorIdentifier = "CamelActorIdentifier"
|
||||
}
|
||||
|
||||
/**
|
||||
* Camel component for sending messages to and receiving replies from (untyped) actors.
|
||||
*
|
||||
* @see akka.camel.component.ActorEndpoint
|
||||
* @see akka.camel.component.ActorProducer
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
class ActorComponent extends DefaultComponent {
|
||||
def createEndpoint(uri: String, remaining: String, parameters: JMap[String, Object]): ActorEndpoint = {
|
||||
val (idType, idValue) = parsePath(remaining)
|
||||
new ActorEndpoint(uri, this, idType, idValue)
|
||||
}
|
||||
|
||||
private def parsePath(remaining: String): Tuple2[String, Option[String]] = remaining match {
|
||||
case null | "" ⇒ throw new IllegalArgumentException("invalid path: [%s] - should be <actorid> or id:<actorid> or uuid:<actoruuid>" format remaining)
|
||||
case id if id startsWith "id:" ⇒ ("id", parseIdentifier(id substring 3))
|
||||
case uuid if uuid startsWith "uuid:" ⇒ ("uuid", parseIdentifier(uuid substring 5))
|
||||
case id ⇒ ("id", parseIdentifier(id))
|
||||
}
|
||||
|
||||
private def parseIdentifier(identifier: String): Option[String] =
|
||||
if (identifier.length > 0) Some(identifier) else None
|
||||
}
|
||||
|
||||
/**
|
||||
* Camel endpoint for sending messages to and receiving replies from (untyped) actors. Actors
|
||||
* are referenced using <code>actor</code> endpoint URIs of the following format:
|
||||
* <code>actor:<actor-id></code>,
|
||||
* <code>actor:id:[<actor-id>]</code> and
|
||||
* <code>actor:uuid:[<actor-uuid>]</code>,
|
||||
* where <code><actor-id></code> refers to <code>ActorRef.id</code> and <code><actor-uuid></code>
|
||||
* refers to the String-representation od <code>ActorRef.uuid</code>. In URIs that contain
|
||||
* <code>id:</code> or <code>uuid:</code>, an actor identifier (id or uuid) is optional. In this
|
||||
* case, the in-message of an exchange produced to this endpoint must contain a message header
|
||||
* with name <code>CamelActorIdentifier</code> and a value that is the target actor's identifier.
|
||||
* If the URI contains an actor identifier, a message with a <code>CamelActorIdentifier</code>
|
||||
* header overrides the identifier in the endpoint URI.
|
||||
*
|
||||
* @see akka.camel.component.ActorComponent
|
||||
* @see akka.camel.component.ActorProducer
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
class ActorEndpoint(uri: String,
|
||||
comp: ActorComponent,
|
||||
val idType: String,
|
||||
val idValue: Option[String]) extends DefaultEndpoint(uri, comp) {
|
||||
|
||||
/**
|
||||
* Whether to block caller thread during two-way message exchanges with (untyped) actors. This is
|
||||
* set via the <code>blocking=true|false</code> endpoint URI parameter. Default value is
|
||||
* <code>false</code>.
|
||||
*/
|
||||
@BeanProperty
|
||||
var blocking: Boolean = false
|
||||
|
||||
/**
|
||||
* Whether to auto-acknowledge one-way message exchanges with (untyped) actors. This is
|
||||
* set via the <code>blocking=true|false</code> endpoint URI parameter. Default value is
|
||||
* <code>true</code>. When set to <code>true</code> consumer actors need to additionally
|
||||
* call <code>Consumer.ack</code> within <code>Actor.receive</code>.
|
||||
*/
|
||||
@BeanProperty
|
||||
var autoack: Boolean = true
|
||||
|
||||
/**
|
||||
* @throws UnsupportedOperationException
|
||||
*/
|
||||
def createConsumer(processor: Processor): Consumer =
|
||||
throw new UnsupportedOperationException("actor consumer not supported yet")
|
||||
|
||||
/**
|
||||
* Creates a new ActorProducer instance initialized with this endpoint.
|
||||
*/
|
||||
def createProducer: ActorProducer = new ActorProducer(this)
|
||||
|
||||
/**
|
||||
* Returns true.
|
||||
*/
|
||||
def isSingleton: Boolean = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the in-message of an exchange to an (untyped) actor, identified by an
|
||||
* actor endpoint URI or by a <code>CamelActorIdentifier</code> message header.
|
||||
* <ul>
|
||||
* <li>If the exchange pattern is out-capable and <code>blocking</code> is set to
|
||||
* <code>true</code> then the producer waits for a reply, using the !! operator.</li>
|
||||
* <li>If the exchange pattern is out-capable and <code>blocking</code> is set to
|
||||
* <code>false</code> then the producer sends the message using the ! operator, together
|
||||
* with a callback handler. The callback handler is an <code>ActorRef</code> that can be
|
||||
* used by the receiving actor to asynchronously reply to the route that is sending the
|
||||
* message.</li>
|
||||
* <li>If the exchange pattern is in-only then the producer sends the message using the
|
||||
* ! operator.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @see akka.camel.component.ActorComponent
|
||||
* @see akka.camel.component.ActorEndpoint
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
class ActorProducer(val ep: ActorEndpoint) extends DefaultProducer(ep) with AsyncProcessor {
|
||||
import ActorProducer._
|
||||
|
||||
private lazy val uuid = uuidFrom(ep.idValue.getOrElse(throw new ActorIdentifierNotSetException))
|
||||
|
||||
def process(exchange: Exchange) =
|
||||
if (exchange.getPattern.isOutCapable) sendSync(exchange) else sendAsync(exchange)
|
||||
|
||||
def process(exchange: Exchange, callback: AsyncCallback): Boolean = {
|
||||
(exchange.getPattern.isOutCapable, ep.blocking, ep.autoack) match {
|
||||
case (true, true, _) ⇒ {
|
||||
sendSync(exchange)
|
||||
callback.done(true)
|
||||
true
|
||||
}
|
||||
case (true, false, _) ⇒ {
|
||||
sendAsync(exchange, Some(AsyncCallbackAdapter(exchange, callback)))
|
||||
false
|
||||
}
|
||||
case (false, false, true) ⇒ {
|
||||
sendAsync(exchange)
|
||||
callback.done(true)
|
||||
true
|
||||
}
|
||||
case (false, false, false) ⇒ {
|
||||
sendAsync(exchange, Some(AsyncCallbackAdapter(exchange, callback)))
|
||||
false
|
||||
}
|
||||
case (false, true, false) ⇒ {
|
||||
sendSync(exchange)
|
||||
callback.done(true)
|
||||
true
|
||||
}
|
||||
case (false, true, true) ⇒ {
|
||||
throw new IllegalStateException("cannot have blocking=true and autoack=true for in-only message exchanges")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def sendSync(exchange: Exchange) = {
|
||||
val actor = target(exchange)
|
||||
val result: Any = try { Some(Await.result((actor ? requestFor(exchange), 5 seconds)) } catch { case e ⇒ Some(Failure(e)) }
|
||||
|
||||
result match {
|
||||
case Some(Ack) ⇒ { /* no response message to set */ }
|
||||
case Some(msg: Failure) ⇒ exchange.fromFailureMessage(msg)
|
||||
case Some(msg) ⇒ exchange.fromResponseMessage(Message.canonicalize(msg))
|
||||
case None ⇒ throw new TimeoutException("timeout (%d ms) while waiting response from %s"
|
||||
format (actor.timeout, ep.getEndpointUri))
|
||||
}
|
||||
}
|
||||
|
||||
private def sendAsync(exchange: Exchange, sender: Option[ActorRef] = None) =
|
||||
target(exchange).!(requestFor(exchange))(sender)
|
||||
|
||||
private def target(exchange: Exchange) =
|
||||
targetOption(exchange) getOrElse (throw new ActorNotRegisteredException(ep.getEndpointUri))
|
||||
|
||||
private def targetOption(exchange: Exchange): Option[ActorRef] = ep.idType match {
|
||||
case "id" ⇒ targetById(targetId(exchange))
|
||||
case "uuid" ⇒ targetByUuid(targetUuid(exchange))
|
||||
}
|
||||
|
||||
private def targetId(exchange: Exchange) = exchange.getIn.getHeader(ActorComponent.ActorIdentifier) match {
|
||||
case id: String ⇒ id
|
||||
case null ⇒ ep.idValue.getOrElse(throw new ActorIdentifierNotSetException)
|
||||
}
|
||||
|
||||
private def targetUuid(exchange: Exchange) = exchange.getIn.getHeader(ActorComponent.ActorIdentifier) match {
|
||||
case uuid: Uuid ⇒ uuid
|
||||
case uuid: String ⇒ uuidFrom(uuid)
|
||||
case null ⇒ uuid
|
||||
}
|
||||
|
||||
private def targetById(id: String) = Actor.registry.local.actorFor(id)
|
||||
private def targetByUuid(uuid: Uuid) = Actor.registry.local.actorFor(uuid)
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
private[camel] object ActorProducer {
|
||||
def requestFor(exchange: Exchange) =
|
||||
exchange.toRequestMessage(Map(Message.MessageExchangeId -> exchange.getExchangeId))
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown to indicate that an actor referenced by an endpoint URI cannot be
|
||||
* found in the Actor.registry.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
class ActorNotRegisteredException(uri: String) extends RuntimeException {
|
||||
override def getMessage = "'%s' not registered" format uri
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown to indicate that no actor identifier has been set.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
class ActorIdentifierNotSetException extends RuntimeException {
|
||||
override def getMessage = "actor identifier not set"
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
private[akka] object AsyncCallbackAdapter {
|
||||
/**
|
||||
* Creates and starts an <code>AsyncCallbackAdapter</code>.
|
||||
*
|
||||
* @param exchange message exchange to write results to.
|
||||
* @param callback callback object to generate completion notifications.
|
||||
*/
|
||||
def apply(exchange: Exchange, callback: AsyncCallback) =
|
||||
new AsyncCallbackAdapter(exchange, callback).start
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapts an <code>ActorRef</code> to a Camel <code>AsyncCallback</code>. Used by receiving actors to reply
|
||||
* asynchronously to Camel routes with <code>ActorRef.reply</code>.
|
||||
* <p>
|
||||
* <em>Please note</em> that this adapter can only be used locally at the moment which should not
|
||||
* be a problem is most situations since Camel endpoints are only activated for local actor references,
|
||||
* never for remote references.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
private[akka] class AsyncCallbackAdapter(exchange: Exchange, callback: AsyncCallback) extends ActorRef with ScalaActorRef {
|
||||
import akka.camel.Consumer._
|
||||
|
||||
val address = exchange.getExchangeId
|
||||
|
||||
@volatile
|
||||
private var running: Boolean = true
|
||||
|
||||
def isTerminated: Boolean = !running
|
||||
|
||||
def suspend(): Unit = ()
|
||||
|
||||
def resume(): Unit = ()
|
||||
|
||||
def stop() { running = false }
|
||||
|
||||
/**
|
||||
* Populates the initial <code>exchange</code> with the reply <code>message</code> and uses the
|
||||
* <code>callback</code> handler to notify Camel about the asynchronous completion of the message
|
||||
* exchange.
|
||||
*
|
||||
* @param message reply message
|
||||
* @param sender ignored
|
||||
*/
|
||||
protected[akka] def postMessageToMailbox(message: Any, sender: ActorRef) = if(running) {
|
||||
message match {
|
||||
case Ack ⇒ { /* no response message to set */ }
|
||||
case msg: Failure ⇒ exchange.fromFailureMessage(msg)
|
||||
case msg ⇒ exchange.fromResponseMessage(Message.canonicalize(msg))
|
||||
}
|
||||
callback.done(false)
|
||||
}
|
||||
|
||||
def ?(message: Any)(implicit timeout: Timeout): Future[Any] =
|
||||
Promise.failed(new UnsupportedOperationException("Ask/? is not supported for %s".format(getClass.getName)))
|
||||
def restart(reason: Throwable): Unit = unsupported
|
||||
|
||||
private def unsupported = throw new UnsupportedOperationException("Not supported for %s" format classOf[AsyncCallbackAdapter].getName)
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camel.internal
|
||||
|
||||
import akka.actor._
|
||||
import collection.mutable.WeakHashMap
|
||||
|
||||
/**
|
||||
* For internal use only. Tracks activation and de-activation of endpoints.
|
||||
*/
|
||||
private[akka] final class ActivationTracker extends Actor with ActorLogging {
|
||||
val activations = new WeakHashMap[ActorRef, ActivationStateMachine]
|
||||
|
||||
/**
|
||||
* A state machine that keeps track of the endpoint activation status of an actor.
|
||||
*/
|
||||
class ActivationStateMachine {
|
||||
type State = PartialFunction[ActivationMessage, Unit]
|
||||
|
||||
var receive: State = notActivated()
|
||||
|
||||
/**
|
||||
* Not activated state
|
||||
* @return a partial function that handles messages in the 'not activated' state
|
||||
*/
|
||||
def notActivated(): State = {
|
||||
var awaitingActivation = List[ActorRef]()
|
||||
var awaitingDeActivation = List[ActorRef]()
|
||||
|
||||
{
|
||||
case AwaitActivation(ref) ⇒ awaitingActivation ::= sender
|
||||
case AwaitDeActivation(ref) ⇒ awaitingDeActivation ::= sender
|
||||
case msg @ EndpointActivated(ref) ⇒
|
||||
awaitingActivation.foreach(_ ! msg)
|
||||
receive = activated(awaitingDeActivation)
|
||||
case EndpointFailedToActivate(ref, cause) ⇒
|
||||
awaitingActivation.foreach(_ ! EndpointFailedToActivate(ref, cause))
|
||||
receive = failedToActivate(cause)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Activated state.
|
||||
* @param currentAwaitingDeActivation the current <code>ActorRef</code>s awaiting de-activation
|
||||
* @return a partial function that handles messages in the 'activated' state
|
||||
*/
|
||||
def activated(currentAwaitingDeActivation: List[ActorRef]): State = {
|
||||
var awaitingDeActivation = currentAwaitingDeActivation
|
||||
|
||||
{
|
||||
case AwaitActivation(ref) ⇒ sender ! EndpointActivated(ref)
|
||||
case AwaitDeActivation(ref) ⇒ awaitingDeActivation ::= sender
|
||||
case msg @ EndpointDeActivated(ref) ⇒
|
||||
awaitingDeActivation foreach (_ ! msg)
|
||||
receive = deactivated
|
||||
case msg @ EndpointFailedToDeActivate(ref, cause) ⇒
|
||||
awaitingDeActivation foreach (_ ! msg)
|
||||
receive = failedToDeActivate(cause)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* De-activated state
|
||||
* @return a partial function that handles messages in the 'de-activated' state
|
||||
*/
|
||||
def deactivated: State = {
|
||||
case AwaitActivation(ref) ⇒ sender ! EndpointActivated(ref)
|
||||
case AwaitDeActivation(ref) ⇒ sender ! EndpointDeActivated(ref)
|
||||
}
|
||||
|
||||
/**
|
||||
* Failed to activate state
|
||||
* @param cause the cause for the failure
|
||||
* @return a partial function that handles messages in 'failed to activate' state
|
||||
*/
|
||||
def failedToActivate(cause: Throwable): State = {
|
||||
case AwaitActivation(ref) ⇒ sender ! EndpointFailedToActivate(ref, cause)
|
||||
case AwaitDeActivation(ref) ⇒ sender ! EndpointFailedToActivate(ref, cause)
|
||||
}
|
||||
|
||||
/**
|
||||
* Failed to de-activate state
|
||||
* @param cause the cause for the failure
|
||||
* @return a partial function that handles messages in 'failed to de-activate' state
|
||||
*/
|
||||
def failedToDeActivate(cause: Throwable): State = {
|
||||
case AwaitActivation(ref) ⇒ sender ! EndpointActivated(ref)
|
||||
case AwaitDeActivation(ref) ⇒ sender ! EndpointFailedToDeActivate(ref, cause)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribes self to messages of type <code>ActivationMessage</code>
|
||||
*/
|
||||
override def preStart() {
|
||||
context.system.eventStream.subscribe(self, classOf[ActivationMessage])
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
override def receive = {
|
||||
case msg @ ActivationMessage(ref) ⇒
|
||||
val state = activations.getOrElseUpdate(ref, new ActivationStateMachine)
|
||||
(state.receive orElse logStateWarning(ref))(msg)
|
||||
}
|
||||
|
||||
private[this] def logStateWarning(actorRef: ActorRef): Receive = { case msg ⇒ log.warning("Message [{}] not expected in current state of actor [{}]", msg, actorRef) }
|
||||
}
|
||||
|
||||
case class AwaitActivation(ref: ActorRef) extends ActivationMessage(ref)
|
||||
|
||||
case class AwaitDeActivation(ref: ActorRef) extends ActivationMessage(ref)
|
||||
|
|
@ -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)
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,190 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camel.internal
|
||||
|
||||
import akka.camel._
|
||||
import component.ActorEndpointPath
|
||||
import java.io.InputStream
|
||||
|
||||
import org.apache.camel.builder.RouteBuilder
|
||||
|
||||
import akka.actor._
|
||||
import collection.mutable
|
||||
import org.apache.camel.model.RouteDefinition
|
||||
import org.apache.camel.CamelContext
|
||||
import akka.util.Duration
|
||||
|
||||
/**
|
||||
* For internal use only.
|
||||
* Manages consumer registration. Consumers call registerConsumer method to register themselves when they get created.
|
||||
* ActorEndpoint uses it to lookup an actor by its path.
|
||||
*/
|
||||
private[camel] trait ConsumerRegistry { this: Activation ⇒
|
||||
def system: ActorSystem
|
||||
def context: CamelContext
|
||||
|
||||
/**
|
||||
* For internal use only.
|
||||
*/
|
||||
private[this] lazy val idempotentRegistry = system.actorOf(Props(new IdempotentCamelConsumerRegistry(context)))
|
||||
/**
|
||||
* For internal use only.
|
||||
* @param endpointUri the URI to register the consumer on
|
||||
* @param consumer the consumer
|
||||
* @param activationTimeout the timeout for activation
|
||||
* @return the actorRef to the consumer
|
||||
*/
|
||||
private[camel] def registerConsumer(endpointUri: String, consumer: Consumer, activationTimeout: Duration) = {
|
||||
idempotentRegistry ! RegisterConsumer(endpointUri, consumer.self, consumer)
|
||||
awaitActivation(consumer.self, activationTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For internal use only.
|
||||
* Guarantees idempotent registration of camel consumer endpoints.
|
||||
*
|
||||
* Once registered the consumer is watched and unregistered upon termination.
|
||||
* It also publishes events to the eventStream so interested parties could subscribe to them.
|
||||
* The main consumer of these events is currently the ActivationTracker.
|
||||
*/
|
||||
private[camel] class IdempotentCamelConsumerRegistry(camelContext: CamelContext) extends Actor {
|
||||
|
||||
case class UnregisterConsumer(actorRef: ActorRef)
|
||||
|
||||
val activated = new mutable.HashSet[ActorRef]
|
||||
|
||||
val registrator = context.actorOf(Props(new CamelConsumerRegistrator))
|
||||
|
||||
def receive = {
|
||||
case msg @ RegisterConsumer(_, consumer, _) ⇒
|
||||
if (!isAlreadyActivated(consumer)) {
|
||||
activated.add(consumer)
|
||||
registrator ! msg
|
||||
}
|
||||
case msg @ EndpointActivated(consumer) ⇒
|
||||
context.watch(consumer)
|
||||
context.system.eventStream.publish(msg)
|
||||
case msg @ EndpointFailedToActivate(consumer, _) ⇒
|
||||
activated.remove(consumer)
|
||||
context.system.eventStream.publish(msg)
|
||||
case Terminated(ref) ⇒
|
||||
activated.remove(ref)
|
||||
registrator ! UnregisterConsumer(ref)
|
||||
case msg @ EndpointFailedToDeActivate(ref, cause) ⇒ context.system.eventStream.publish(msg)
|
||||
case msg: EndpointDeActivated ⇒ context.system.eventStream.publish(msg)
|
||||
}
|
||||
|
||||
def isAlreadyActivated(ref: ActorRef): Boolean = activated.contains(ref)
|
||||
|
||||
class CamelConsumerRegistrator extends Actor with ActorLogging {
|
||||
|
||||
def receive = {
|
||||
case RegisterConsumer(endpointUri, consumer, consumerConfig) ⇒
|
||||
camelContext.addRoutes(new ConsumerActorRouteBuilder(endpointUri, consumer, consumerConfig))
|
||||
context.sender ! EndpointActivated(consumer)
|
||||
log.debug("Published actor [{}] at endpoint [{}]", consumerConfig, endpointUri)
|
||||
case UnregisterConsumer(consumer) ⇒
|
||||
camelContext.stopRoute(consumer.path.toString)
|
||||
context.sender ! EndpointDeActivated(consumer)
|
||||
log.debug("Unpublished actor [{}] from endpoint [{}]", consumer, consumer.path)
|
||||
}
|
||||
|
||||
override def preRestart(reason: Throwable, message: Option[Any]) {
|
||||
//FIXME check logic
|
||||
super.preStart()
|
||||
message match {
|
||||
case Some(RegisterConsumer(_, consumer, _)) ⇒ sender ! EndpointFailedToActivate(consumer, reason)
|
||||
case Some(UnregisterConsumer(consumer)) ⇒ sender ! EndpointFailedToDeActivate(consumer, reason)
|
||||
case _ ⇒
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For internal use only. A message to register a consumer.
|
||||
* @param endpointUri the endpointUri to register to
|
||||
* @param actorRef the actorRef to register as a consumer
|
||||
* @param config the configuration for the consumer
|
||||
*/
|
||||
private[camel] case class RegisterConsumer(endpointUri: String, actorRef: ActorRef, config: ConsumerConfig)
|
||||
|
||||
/**
|
||||
* For internal use only.
|
||||
* Abstract builder of a route to a target which can be either an actor or an typed actor method.
|
||||
*
|
||||
* @param endpointUri endpoint URI of the consumer actor or typed actor method.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
private[camel] class ConsumerActorRouteBuilder(endpointUri: String, consumer: ActorRef, config: ConsumerConfig) extends RouteBuilder {
|
||||
|
||||
//TODO: what if actorpath contains parameters? Should we use url encoding? But this will look ugly...
|
||||
protected def targetActorUri = ActorEndpointPath(consumer).toCamelPath(config)
|
||||
|
||||
def configure() {
|
||||
val scheme = endpointUri take endpointUri.indexOf(":") // e.g. "http" from "http://whatever/..."
|
||||
|
||||
val route = from(endpointUri).routeId(consumer.path.toString)
|
||||
val converted = Conversions(scheme, route)
|
||||
val userCustomized = applyUserRouteCustomization(converted)
|
||||
userCustomized.to(targetActorUri)
|
||||
}
|
||||
|
||||
def applyUserRouteCustomization(rd: RouteDefinition) = config.onRouteDefinition(rd)
|
||||
|
||||
object Conversions {
|
||||
// TODO: make conversions configurable
|
||||
private val bodyConversions = Map(
|
||||
"file" -> classOf[InputStream])
|
||||
|
||||
def apply(scheme: String, routeDefinition: RouteDefinition): RouteDefinition = bodyConversions.get(scheme) match {
|
||||
case Some(clazz) ⇒ routeDefinition.convertBodyTo(clazz)
|
||||
case None ⇒ routeDefinition
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Super class of all activation messages.
|
||||
*/
|
||||
abstract class ActivationMessage(val actor: ActorRef)
|
||||
|
||||
/**
|
||||
* For internal use only. companion object of <code>ActivationMessage</code>
|
||||
*
|
||||
*/
|
||||
private[camel] object ActivationMessage {
|
||||
def unapply(msg: ActivationMessage): Option[ActorRef] = Some(msg.actor)
|
||||
}
|
||||
|
||||
/**
|
||||
* For internal use only.
|
||||
* Event message indicating that a single endpoint has been activated.
|
||||
*/
|
||||
sealed case class EndpointActivated(actorRef: ActorRef) extends ActivationMessage(actorRef)
|
||||
|
||||
/**
|
||||
* For internal use only.
|
||||
* Event message indicating that a single endpoint failed tp activate
|
||||
* @param actorRef the endpoint that failed to activate
|
||||
* @param cause the cause for failure
|
||||
*/
|
||||
sealed case class EndpointFailedToActivate(actorRef: ActorRef, cause: Throwable) extends ActivationMessage(actorRef)
|
||||
|
||||
/**
|
||||
* For internal use only.
|
||||
* @param actorRef the endpoint that was de-activated
|
||||
*/
|
||||
sealed case class EndpointDeActivated(actorRef: ActorRef) extends ActivationMessage(actorRef)
|
||||
|
||||
/**
|
||||
* For internal use only.
|
||||
* @param actorRef the endpoint that failed to de-activate
|
||||
* @param cause the cause for failure
|
||||
*/
|
||||
sealed case class EndpointFailedToDeActivate(actorRef: ActorRef, cause: Throwable) extends ActivationMessage(actorRef)
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
package akka.camel.internal
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import org.apache.camel.processor.SendProcessor
|
||||
import akka.actor.{ Props, ActorRef, Terminated, Actor }
|
||||
import org.apache.camel.Endpoint
|
||||
import akka.camel.Camel
|
||||
import akka.util.NonFatal
|
||||
|
||||
/**
|
||||
* Watches the end of life of <code>Producer</code>s.
|
||||
* Removes a <code>Producer</code> from the <code>ProducerRegistry</code> when it is <code>Terminated</code>,
|
||||
* which in turn stops the <code>SendProcessor</code>.
|
||||
*/
|
||||
private class ProducerWatcher(registry: ProducerRegistry) extends Actor {
|
||||
override def receive = {
|
||||
case RegisterProducer(actorRef) ⇒ context.watch(actorRef)
|
||||
case Terminated(actorRef) ⇒ registry.unregisterProducer(actorRef)
|
||||
}
|
||||
}
|
||||
|
||||
private case class RegisterProducer(actorRef: ActorRef)
|
||||
|
||||
/**
|
||||
* For internal use only.
|
||||
* Manages the Camel objects for <code>Producer</code>s.
|
||||
* Every <code>Producer</code> needs an <code>Endpoint</code> and a <code>SendProcessor</code>
|
||||
* to produce messages over an <code>Exchange</code>.
|
||||
*/
|
||||
private[camel] trait ProducerRegistry {
|
||||
this: Camel ⇒
|
||||
private val camelObjects = new ConcurrentHashMap[ActorRef, (Endpoint, SendProcessor)]()
|
||||
private val watcher = system.actorOf(Props(new ProducerWatcher(this)))
|
||||
|
||||
private def registerWatch(actorRef: ActorRef) {
|
||||
watcher ! RegisterProducer(actorRef)
|
||||
}
|
||||
|
||||
/**
|
||||
* For internal use only.
|
||||
* Unregisters <code>Endpoint</code> and <code>SendProcessor</code> and stops the SendProcessor
|
||||
*/
|
||||
private[camel] def unregisterProducer(actorRef: ActorRef): Unit = {
|
||||
// Terminated cannot be sent before the actor is created in the processing of system messages.
|
||||
Option(camelObjects.remove(actorRef)).foreach {
|
||||
case (_, processor) ⇒
|
||||
try {
|
||||
processor.stop()
|
||||
system.eventStream.publish(EndpointDeActivated(actorRef))
|
||||
} catch {
|
||||
case NonFatal(e) ⇒ system.eventStream.publish(EndpointFailedToDeActivate(actorRef, e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For internal use only.
|
||||
* Creates <code>Endpoint</code> and <code>SendProcessor</code> and associates the actorRef to these.
|
||||
* @param actorRef the actorRef of the <code>Producer</code> actor.
|
||||
* @param endpointUri the endpoint Uri of the producer
|
||||
* @return <code>Endpoint</code> and <code>SendProcessor</code> registered for the actorRef
|
||||
*/
|
||||
private[camel] def registerProducer(actorRef: ActorRef, endpointUri: String): (Endpoint, SendProcessor) = {
|
||||
try {
|
||||
val endpoint = context.getEndpoint(endpointUri)
|
||||
val processor = new SendProcessor(endpoint)
|
||||
|
||||
camelObjects.putIfAbsent(actorRef, (endpoint, processor)) match {
|
||||
case null ⇒
|
||||
processor.start()
|
||||
registerWatch(actorRef)
|
||||
system.eventStream.publish(EndpointActivated(actorRef))
|
||||
(endpoint, processor)
|
||||
case prev ⇒ prev
|
||||
}
|
||||
} catch {
|
||||
case NonFatal(e) ⇒ {
|
||||
system.eventStream.publish(EndpointFailedToActivate(actorRef, e))
|
||||
// can't return null to the producer actor, so blow up actor in initialization.
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,261 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camel.internal.component
|
||||
|
||||
import java.util.{ Map ⇒ JMap }
|
||||
|
||||
import org.apache.camel._
|
||||
import org.apache.camel.impl.{ DefaultProducer, DefaultEndpoint, DefaultComponent }
|
||||
|
||||
import akka.actor._
|
||||
import akka.pattern._
|
||||
|
||||
import scala.reflect.BeanProperty
|
||||
import akka.util.duration._
|
||||
import java.util.concurrent.{ TimeoutException, CountDownLatch }
|
||||
import akka.camel.internal.CamelExchangeAdapter
|
||||
import akka.util.{ NonFatal, Duration, Timeout }
|
||||
import akka.camel.{ DefaultConsumerConfig, ConsumerConfig, Camel, Ack, Failure ⇒ CamelFailure, CamelMessage }
|
||||
|
||||
/**
|
||||
* For internal use only.
|
||||
* Camel component for sending messages to and receiving replies from (untyped) actors.
|
||||
*
|
||||
* @see akka.camel.component.ActorEndpoint
|
||||
* @see akka.camel.component.ActorProducer
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
private[camel] class ActorComponent(camel: Camel) extends DefaultComponent {
|
||||
def createEndpoint(uri: String, remaining: String, parameters: JMap[String, Object]): ActorEndpoint = {
|
||||
val path = ActorEndpointPath.fromCamelPath(remaining)
|
||||
new ActorEndpoint(uri, this, path, camel)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For internal use only.
|
||||
* The ActorEndpoint is a Camel Endpoint that is used to receive messages from Camel through the ActorComponent
|
||||
* Actors are referenced using <code>actor</code> endpoint URIs of the following format:
|
||||
* <code>actor://path:[actorPath]?[options]%s</code>,
|
||||
* where <code>[actorPath]</code> refers to the Actor Path to the Actor.
|
||||
*
|
||||
* @see akka.camel.component.ActorComponent
|
||||
* @see akka.camel.component.ActorProducer
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
private[camel] class ActorEndpoint(uri: String,
|
||||
comp: ActorComponent,
|
||||
val path: ActorEndpointPath,
|
||||
camel: Camel) extends DefaultEndpoint(uri, comp) with ActorEndpointConfig {
|
||||
|
||||
/**
|
||||
*
|
||||
* The ActorEndpoint right now only supports receiving messages from Camel.
|
||||
* The createProducer method (not to be confused with a producer actor) is used to send messages into the endpoint.
|
||||
* The ActorComponent is only there to send to actors registered through an actor endpoint URI.
|
||||
* You can use an actor as an endpoint to send to in a camel route (as in, a Camel Consumer Actor). so from(someuri) to (actoruri), but not 'the other way around'.
|
||||
* Supporting createConsumer would mean that messages are consumed from an Actor endpoint in a route, and an Actor is not necessarily a producer of messages
|
||||
* [[akka.camel.Producer]] Actors can be used for sending messages to some other uri/ component type registered in Camel.
|
||||
* @throws UnsupportedOperationException this method is not supported
|
||||
*/
|
||||
def createConsumer(processor: Processor): org.apache.camel.Consumer =
|
||||
throw new UnsupportedOperationException("actor consumer not supported yet")
|
||||
|
||||
/**
|
||||
* Creates a new ActorProducer instance initialized with this endpoint.
|
||||
*/
|
||||
def createProducer: ActorProducer = new ActorProducer(this, camel)
|
||||
|
||||
/**
|
||||
* Returns true.
|
||||
*/
|
||||
def isSingleton: Boolean = true
|
||||
}
|
||||
|
||||
private[camel] trait ActorEndpointConfig {
|
||||
def path: ActorEndpointPath
|
||||
|
||||
@BeanProperty var replyTimeout: Duration = 1 minute
|
||||
|
||||
/**
|
||||
* TODO fix it
|
||||
* Whether to auto-acknowledge one-way message exchanges with (untyped) actors. This is
|
||||
* set via the <code>blocking=true|false</code> endpoint URI parameter. Default value is
|
||||
* <code>true</code>. When set to <code>true</code> consumer actors need to additionally
|
||||
* call <code>Consumer.ack</code> within <code>Actor.receive</code>.
|
||||
*/
|
||||
@BeanProperty var autoack: Boolean = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the in-message of an exchange to an untyped actor, identified by an [[akka.camel.internal.component.ActorEndPoint]]
|
||||
*
|
||||
* @see akka.camel.component.ActorComponent
|
||||
* @see akka.camel.component.ActorEndpoint
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
private[camel] class ActorProducer(val endpoint: ActorEndpoint, camel: Camel) extends DefaultProducer(endpoint) with AsyncProcessor {
|
||||
/**
|
||||
* Processes the exchange.
|
||||
* Calls the asynchronous version of the method and waits for the result (blocking)
|
||||
* @param exchange the exchange to process
|
||||
*/
|
||||
def process(exchange: Exchange) { processExchangeAdapter(new CamelExchangeAdapter(exchange)) }
|
||||
|
||||
/**
|
||||
* Processes the message exchange. the caller supports having the exchange asynchronously processed.
|
||||
* If there was a failure processing then the caused Exception would be set on the Exchange.
|
||||
*
|
||||
* @param exchange the message exchange
|
||||
* @param callback the AsyncCallback will be invoked when the processing of the exchange is completed.
|
||||
* If the exchange is completed synchronously, then the callback is also invoked synchronously.
|
||||
* The callback should therefore be careful of starting recursive loop.
|
||||
* @return (doneSync) true to continue execute synchronously, false to continue being executed asynchronously
|
||||
*/
|
||||
def process(exchange: Exchange, callback: AsyncCallback): Boolean = { processExchangeAdapter(new CamelExchangeAdapter(exchange), callback) }
|
||||
|
||||
/**
|
||||
* For internal use only. Processes the [[akka.camel.internal.CamelExchangeAdapter]]
|
||||
* @param exchange the [[akka.camel.internal.CamelExchangeAdapter]]
|
||||
*/
|
||||
private[camel] def processExchangeAdapter(exchange: CamelExchangeAdapter) {
|
||||
val isDone = new CountDownLatch(1)
|
||||
processExchangeAdapter(exchange, new AsyncCallback { def done(doneSync: Boolean) { isDone.countDown() } })
|
||||
isDone.await() // this should never wait forever as the process(exchange, callback) method guarantees that.
|
||||
}
|
||||
|
||||
/**
|
||||
* For internal use only. Processes the [[akka.camel.internal.CamelExchangeAdapter]].
|
||||
* This method is blocking when the exchange is inOnly. The method returns true if it executed synchronously/blocking.
|
||||
* @param exchange the [[akka.camel.internal.CamelExchangeAdapter]]
|
||||
* @param callback the callback
|
||||
* @return (doneSync) true to continue execute synchronously, false to continue being executed asynchronously
|
||||
*/
|
||||
private[camel] def processExchangeAdapter(exchange: CamelExchangeAdapter, callback: AsyncCallback): Boolean = {
|
||||
|
||||
// these notify methods are just a syntax sugar
|
||||
def notifyDoneSynchronously[A](a: A = null) = callback.done(true)
|
||||
def notifyDoneAsynchronously[A](a: A = null) = callback.done(false)
|
||||
|
||||
def message = messageFor(exchange)
|
||||
|
||||
if (exchange.isOutCapable) { //InOut
|
||||
sendAsync(message, onComplete = forwardResponseTo(exchange) andThen notifyDoneAsynchronously)
|
||||
} else { // inOnly
|
||||
if (endpoint.autoack) { //autoAck
|
||||
fireAndForget(message, exchange)
|
||||
notifyDoneSynchronously()
|
||||
true // done sync
|
||||
} else { //manualAck
|
||||
sendAsync(message, onComplete = forwardAckTo(exchange) andThen notifyDoneAsynchronously)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
private def forwardResponseTo(exchange: CamelExchangeAdapter): PartialFunction[Either[Throwable, Any], Unit] = {
|
||||
case Right(failure: CamelFailure) ⇒ exchange.setFailure(failure);
|
||||
case Right(msg) ⇒ exchange.setResponse(CamelMessage.canonicalize(msg))
|
||||
case Left(e: TimeoutException) ⇒ exchange.setFailure(CamelFailure(new TimeoutException("Failed to get response from the actor [%s] within timeout [%s]. Check replyTimeout and blocking settings [%s]" format (endpoint.path, endpoint.replyTimeout, endpoint))))
|
||||
case Left(throwable) ⇒ exchange.setFailure(CamelFailure(throwable))
|
||||
}
|
||||
|
||||
private def forwardAckTo(exchange: CamelExchangeAdapter): PartialFunction[Either[Throwable, Any], Unit] = {
|
||||
case Right(Ack) ⇒ { /* no response message to set */ }
|
||||
case Right(failure: CamelFailure) ⇒ exchange.setFailure(failure)
|
||||
case Right(msg) ⇒ exchange.setFailure(CamelFailure(new IllegalArgumentException("Expected Ack or Failure message, but got: [%s] from actor [%s]" format (msg, endpoint.path))))
|
||||
case Left(e: TimeoutException) ⇒ exchange.setFailure(CamelFailure(new TimeoutException("Failed to get Ack or Failure response from the actor [%s] within timeout [%s]. Check replyTimeout and blocking settings [%s]" format (endpoint.path, endpoint.replyTimeout, endpoint))))
|
||||
case Left(throwable) ⇒ exchange.setFailure(CamelFailure(throwable))
|
||||
}
|
||||
|
||||
private def sendAsync(message: CamelMessage, onComplete: PartialFunction[Either[Throwable, Any], Unit]): Boolean = {
|
||||
try {
|
||||
val actor = actorFor(endpoint.path)
|
||||
val future = actor.ask(message)(new Timeout(endpoint.replyTimeout))
|
||||
future.onComplete(onComplete)
|
||||
} catch {
|
||||
case NonFatal(e) ⇒ onComplete(Left(e))
|
||||
}
|
||||
false // Done async
|
||||
}
|
||||
|
||||
private def fireAndForget(message: CamelMessage, exchange: CamelExchangeAdapter) {
|
||||
try {
|
||||
actorFor(endpoint.path) ! message
|
||||
} catch {
|
||||
case e ⇒ exchange.setFailure(new CamelFailure(e))
|
||||
}
|
||||
}
|
||||
|
||||
private[this] def actorFor(path: ActorEndpointPath): ActorRef =
|
||||
path.findActorIn(camel.system) getOrElse (throw new ActorNotRegisteredException(path.actorPath))
|
||||
|
||||
private[this] def messageFor(exchange: CamelExchangeAdapter) =
|
||||
exchange.toRequestMessage(Map(CamelMessage.MessageExchangeId -> exchange.getExchangeId))
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown to indicate that an actor referenced by an endpoint URI cannot be
|
||||
* found in the Actor.registry.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
class ActorNotRegisteredException(uri: String) extends RuntimeException {
|
||||
override def getMessage = "Actor [%s] doesn't exist" format uri
|
||||
}
|
||||
|
||||
/**
|
||||
* For internal use only.
|
||||
*/
|
||||
private[camel] object DurationTypeConverter extends CamelTypeConverter {
|
||||
def convertTo[T](`type`: Class[T], value: AnyRef) = {
|
||||
Duration(value.toString).asInstanceOf[T]
|
||||
}
|
||||
|
||||
def toString(duration: Duration) = duration.toNanos + " nanos"
|
||||
}
|
||||
|
||||
/**
|
||||
* For internal use only.
|
||||
*/
|
||||
private[camel] abstract class CamelTypeConverter extends TypeConverter {
|
||||
def convertTo[T](`type`: Class[T], exchange: Exchange, value: AnyRef) = convertTo(`type`, value)
|
||||
def mandatoryConvertTo[T](`type`: Class[T], value: AnyRef) = convertTo(`type`, value)
|
||||
def mandatoryConvertTo[T](`type`: Class[T], exchange: Exchange, value: AnyRef) = convertTo(`type`, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* For internal use only. An endpoint to an <code>ActorRef</code>
|
||||
* @param actorPath the path to the actor
|
||||
*/
|
||||
private[camel] case class ActorEndpointPath private (actorPath: String) {
|
||||
require(actorPath != null)
|
||||
require(actorPath.length() > 0)
|
||||
def toCamelPath(config: ConsumerConfig = DefaultConsumerConfig): String = "actor://path:%s?%s" format (actorPath, config.toCamelParameters)
|
||||
|
||||
def findActorIn(system: ActorSystem): Option[ActorRef] = {
|
||||
val ref = system.actorFor(actorPath)
|
||||
if (ref.isTerminated) None else Some(ref)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* For internal use only. Companion of <code>ActorEndpointPath</code>
|
||||
*/
|
||||
private[camel] object ActorEndpointPath {
|
||||
def apply(actorRef: ActorRef) = new ActorEndpointPath(actorRef.path.toString)
|
||||
|
||||
/**
|
||||
* Expects path in a format: path:%s
|
||||
*/
|
||||
def fromCamelPath(camelPath: String) = camelPath match {
|
||||
case id if id startsWith "path:" ⇒ new ActorEndpointPath(id substring 5)
|
||||
case _ ⇒ throw new IllegalArgumentException("Invalid path: [%s] - should be path:<actorPath>" format camelPath)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camel.javaapi
|
||||
|
||||
import akka.actor.UntypedActor
|
||||
import akka.camel._
|
||||
import org.apache.camel.{ ProducerTemplate, CamelContext }
|
||||
|
||||
/**
|
||||
* Java-friendly Consumer.
|
||||
*
|
||||
* @see UntypedConsumerActor
|
||||
* @see RemoteUntypedConsumerActor
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
trait UntypedConsumer extends Consumer { self: UntypedActor ⇒
|
||||
final def endpointUri = getEndpointUri
|
||||
|
||||
/**
|
||||
* Returns the Camel endpoint URI to consume messages from.
|
||||
*/
|
||||
def getEndpointUri(): String
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclass this abstract class to create an MDB-style untyped consumer actor. This
|
||||
* class is meant to be used from Java.
|
||||
*/
|
||||
abstract class UntypedConsumerActor extends UntypedActor with UntypedConsumer {
|
||||
/**
|
||||
* Returns the [[org.apache.camel.CamelContext]]
|
||||
* @return the CamelContext
|
||||
*/
|
||||
protected def getCamelContext: CamelContext = camelContext
|
||||
|
||||
/**
|
||||
* Returns the [[org.apache.camel.ProducerTemplate]]
|
||||
* @return the ProducerTemplate
|
||||
*/
|
||||
protected def getProducerTemplate: ProducerTemplate = camel.template
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camel.javaapi
|
||||
|
||||
import akka.actor.UntypedActor
|
||||
import akka.camel._
|
||||
import org.apache.camel.{ CamelContext, ProducerTemplate }
|
||||
|
||||
/**
|
||||
* Subclass this abstract class to create an untyped producer actor. This class is meant to be used from Java.
|
||||
*
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
abstract class UntypedProducerActor extends UntypedActor with ProducerSupport {
|
||||
/**
|
||||
* Called before the message is sent to the endpoint specified by <code>getEndpointUri</code>. The original
|
||||
* message is passed as argument. By default, this method simply returns the argument but may be overridden
|
||||
* by subclasses.
|
||||
*/
|
||||
def onReceiveBeforeProduce(message: AnyRef): AnyRef = message
|
||||
|
||||
/**
|
||||
* Called after a response was received from the endpoint specified by <code>endpointUri</code>. The
|
||||
* response is passed as argument. By default, this method sends the response back to the original sender
|
||||
* if <code>oneway</code> is <code>false</code>. If <code>oneway</code> is <code>true</code>, nothing is
|
||||
* done. This method may be overridden by subclasses (e.g. to forward responses to another actor).
|
||||
*/
|
||||
def onReceiveAfterProduce(message: AnyRef): Unit = super.receiveAfterProduce(message)
|
||||
|
||||
final override def receiveBeforeProduce = {
|
||||
case msg: AnyRef ⇒ onReceiveBeforeProduce(msg)
|
||||
}
|
||||
|
||||
final override def receiveAfterProduce = {
|
||||
case msg: AnyRef ⇒ onReceiveAfterProduce(msg)
|
||||
}
|
||||
|
||||
final override def endpointUri = getEndpointUri
|
||||
|
||||
final override def oneway = isOneway
|
||||
|
||||
/**
|
||||
* Default implementation of UntypedActor.onReceive
|
||||
*/
|
||||
def onReceive(message: Any) {
|
||||
produce(message)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Camel endpoint URI to produce messages to.
|
||||
*/
|
||||
def getEndpointUri(): String
|
||||
|
||||
/**
|
||||
* If set to false (default), this producer expects a response message from the Camel endpoint.
|
||||
* If set to true, this producer communicates with the Camel endpoint with an in-only message
|
||||
* exchange pattern (fire and forget).
|
||||
*/
|
||||
def isOneway() = super.oneway
|
||||
|
||||
/**
|
||||
* Returns the <code>CamelContext</code>.
|
||||
*/
|
||||
def getCamelContext(): CamelContext = camel.context
|
||||
|
||||
/**
|
||||
* Returns the <code>ProducerTemplate</code>.
|
||||
*/
|
||||
def getProducerTemplate(): ProducerTemplate = camel.template
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camelexamples
|
||||
|
||||
import akka.camel._
|
||||
import akka.util.duration._
|
||||
import akka.actor.{ Actor, OneForOneStrategy }
|
||||
import akka.actor.SupervisorStrategy._
|
||||
|
||||
object ExamplesSupport {
|
||||
val retry3xWithin1s = OneForOneStrategy(maxNrOfRetries = 3, withinTimeRange = 1 second) {
|
||||
case _: Exception ⇒ Restart
|
||||
}
|
||||
}
|
||||
|
||||
class SysOutConsumer extends Consumer {
|
||||
override def activationTimeout = 10 seconds
|
||||
def endpointUri = "file://data/input/CamelConsumer"
|
||||
|
||||
protected def receive = {
|
||||
case msg: CamelMessage ⇒ {
|
||||
printf("Received '%s'\n", msg.bodyAs[String])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TroubleMaker extends Consumer {
|
||||
def endpointUri = "WRONG URI"
|
||||
|
||||
println("Trying to instantiate conumer with uri: " + endpointUri)
|
||||
protected def receive = { case _ ⇒ }
|
||||
}
|
||||
|
||||
class SysOutActor(implicit camel: Camel) extends Actor {
|
||||
implicit val camelContext = camel.context
|
||||
protected def receive = {
|
||||
case msg: CamelMessage ⇒ {
|
||||
printf("Received '%s'\n", msg.bodyAs[String])
|
||||
}
|
||||
}
|
||||
}
|
||||
1
akka-camel/src/main/scala/akka/camelexamples/README.txt
Normal file
1
akka-camel/src/main/scala/akka/camelexamples/README.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
This package is outside of akka.camel because we don't want to use private[camel] features in examples.
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camelexamples
|
||||
|
||||
import java.io.FileWriter
|
||||
|
||||
object RichString {
|
||||
implicit def toRichString(s: String): RichString = new RichString(s)
|
||||
}
|
||||
|
||||
class RichString(s: String) {
|
||||
def saveAs(fileName: String) = write(fileName, s)
|
||||
def >>(fileName: String) = this.saveAs(fileName)
|
||||
def <<(content: String) = write(s, content)
|
||||
|
||||
private[this] def write(fileName: String, content: String) {
|
||||
val f = new FileWriter(fileName)
|
||||
f.write(content)
|
||||
f.close()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camelexamples
|
||||
|
||||
import akka.actor.{ Props, ActorSystem }
|
||||
import RichString._
|
||||
|
||||
object _1_SimpleConsumer extends App {
|
||||
val system = ActorSystem("test")
|
||||
|
||||
system.actorOf(Props[SysOutConsumer])
|
||||
|
||||
"data/input/CamelConsumer/file1.txt" << "test data " + math.random
|
||||
|
||||
Thread.sleep(2000)
|
||||
|
||||
system.shutdown()
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camelexamples
|
||||
|
||||
import akka.actor.{ PoisonPill, Terminated, Props, ActorSystem, Actor }
|
||||
import ExamplesSupport._
|
||||
import RichString._
|
||||
|
||||
object SupervisedConsumersExample extends App {
|
||||
|
||||
val system = ActorSystem("test1")
|
||||
|
||||
system.actorOf(Props(new Actor {
|
||||
context.watch(context.actorOf(Props[EndpointManager]))
|
||||
protected def receive = {
|
||||
case Terminated(ref) ⇒ system.shutdown()
|
||||
}
|
||||
}))
|
||||
|
||||
"data/input/CamelConsumer/file1.txt" << "test data " + math.random
|
||||
}
|
||||
|
||||
class EndpointManager extends Actor {
|
||||
import context._
|
||||
|
||||
override def supervisorStrategy() = retry3xWithin1s
|
||||
|
||||
watch(actorOf(Props[SysOutConsumer]))
|
||||
watch(actorOf(Props[TroubleMaker]))
|
||||
|
||||
protected def receive = {
|
||||
case Terminated(ref) ⇒ {
|
||||
printf("Hey! One of the endpoints has died: %s. I am doing sepuku...\n", ref)
|
||||
self ! PoisonPill
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camelexamples
|
||||
|
||||
import org.apache.camel.builder.RouteBuilder
|
||||
import akka.actor.{ Props, ActorSystem }
|
||||
import akka.camel._
|
||||
import RichString._
|
||||
|
||||
object _3_SimpleActorEndpoint extends App {
|
||||
|
||||
val system = ActorSystem("test")
|
||||
val camel = CamelExtension(system)
|
||||
|
||||
val actor = system.actorOf(Props[SysOutActor])
|
||||
|
||||
camel.context.addRoutes(new RouteBuilder() {
|
||||
def configure() {
|
||||
from("file://data/input/CamelConsumer").to(actor)
|
||||
}
|
||||
})
|
||||
|
||||
"data/input/CamelConsumer/file1.txt" << "test data " + math.random
|
||||
|
||||
Thread.sleep(3000)
|
||||
|
||||
system.shutdown()
|
||||
|
||||
}
|
||||
11
akka-camel/src/main/scala/akka/package.scala
Normal file
11
akka-camel/src/main/scala/akka/package.scala
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka
|
||||
|
||||
import org.apache.camel.model.ProcessorDefinition
|
||||
|
||||
package object camel {
|
||||
implicit def toActorRouteDefinition(definition: ProcessorDefinition[_]) = new ActorRouteDefinition(definition)
|
||||
}
|
||||
|
|
@ -1,46 +1,41 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camel;
|
||||
|
||||
import akka.japi.SideEffect;
|
||||
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.actor.Props;
|
||||
import akka.util.FiniteDuration;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import static akka.actor.Actors.*;
|
||||
import static akka.camel.CamelContextManager.*;
|
||||
import static akka.camel.CamelServiceManager.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
public class ConsumerJavaTestBase {
|
||||
|
||||
@BeforeClass
|
||||
public static void setUpBeforeClass() {
|
||||
startCamelService();
|
||||
}
|
||||
static ActorSystem system = ActorSystem.create("test");
|
||||
static Camel camel = (Camel) CamelExtension.get(system);
|
||||
|
||||
|
||||
@AfterClass
|
||||
public static void tearDownAfterClass() {
|
||||
stopCamelService();
|
||||
registry().local().shutdownAll();
|
||||
system.shutdown();
|
||||
}
|
||||
|
||||
@Test @Ignore // TODO: fix race
|
||||
|
||||
// org.apache.camel.CamelExchangeException: No consumers available
|
||||
// on endpoint: Endpoint[direct://error-handler-test-java]
|
||||
|
||||
@Test
|
||||
public void shouldHandleExceptionThrownByActorAndGenerateCustomResponse() {
|
||||
getMandatoryService().awaitEndpointActivation(1, new SideEffect() {
|
||||
public void apply() {
|
||||
actorOf(SampleErrorHandlingConsumer.class);
|
||||
}
|
||||
});
|
||||
String result = getMandatoryTemplate().requestBody("direct:error-handler-test-java", "hello", String.class);
|
||||
ActorRef ref = system.actorOf(new Props().withCreator(SampleErrorHandlingConsumer.class));
|
||||
camel.awaitActivation(ref, new FiniteDuration(1, TimeUnit.SECONDS));
|
||||
|
||||
String result = camel.template().requestBody("direct:error-handler-test-java", "hello", String.class);
|
||||
assertEquals("error: hello", result);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,109 +1,126 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camel;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.japi.Function;
|
||||
import org.apache.camel.NoTypeConversionAvailableException;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import akka.camel.CamelContextManager;
|
||||
import akka.camel.Message;
|
||||
import akka.japi.Function;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.*;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
public class MessageJavaTestBase {
|
||||
static Camel camel;
|
||||
private static ActorSystem system;
|
||||
private Map<String,Object> empty = new HashMap<String, Object>();
|
||||
|
||||
@BeforeClass
|
||||
public static void setUpBeforeClass() {
|
||||
CamelContextManager.init();
|
||||
system = ActorSystem.create("test");
|
||||
camel = (Camel) CamelExtension.get(system);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void cleanup(){
|
||||
system.shutdown();
|
||||
}
|
||||
|
||||
CamelMessage message(Object body){ return new CamelMessage(body, new HashMap<String, Object>()); }
|
||||
CamelMessage message(Object body, Map<String, Object> headers){ return new CamelMessage(body, headers); }
|
||||
|
||||
|
||||
@Test public void shouldConvertDoubleBodyToString() {
|
||||
assertEquals("1.4", new Message("1.4").getBodyAs(String.class));
|
||||
assertEquals("1.4", message("1.4", empty).getBodyAs(String.class,camel.context()));
|
||||
}
|
||||
|
||||
@Test(expected=NoTypeConversionAvailableException.class)
|
||||
public void shouldThrowExceptionWhenConvertingDoubleBodyToInputStream() {
|
||||
new Message(1.4).getBodyAs(InputStream.class);
|
||||
message(1.4).getBodyAs(InputStream.class,camel.context());
|
||||
}
|
||||
|
||||
|
||||
@Test public void shouldReturnDoubleHeader() {
|
||||
Message message = new Message("test" , createMap("test", 1.4));
|
||||
CamelMessage message = message("test" , createMap("test", 1.4));
|
||||
assertEquals(1.4, message.getHeader("test"));
|
||||
}
|
||||
|
||||
@Test public void shouldConvertDoubleHeaderToString() {
|
||||
Message message = new Message("test" , createMap("test", 1.4));
|
||||
assertEquals("1.4", message.getHeaderAs("test", String.class));
|
||||
CamelMessage message = message("test" , createMap("test", 1.4));
|
||||
assertEquals("1.4", message.getHeaderAs("test", String.class,camel.context()));
|
||||
}
|
||||
|
||||
@Test public void shouldReturnSubsetOfHeaders() {
|
||||
Message message = new Message("test" , createMap("A", "1", "B", "2"));
|
||||
CamelMessage message = message("test" , createMap("A", "1", "B", "2"));
|
||||
assertEquals(createMap("B", "2"), message.getHeaders(createSet("B")));
|
||||
}
|
||||
|
||||
@Test(expected=UnsupportedOperationException.class)
|
||||
public void shouldReturnSubsetOfHeadersUnmodifiable() {
|
||||
Message message = new Message("test" , createMap("A", "1", "B", "2"));
|
||||
CamelMessage message = message("test" , createMap("A", "1", "B", "2"));
|
||||
message.getHeaders(createSet("B")).put("x", "y");
|
||||
}
|
||||
|
||||
@Test public void shouldReturnAllHeaders() {
|
||||
Message message = new Message("test" , createMap("A", "1", "B", "2"));
|
||||
CamelMessage message = message("test" , createMap("A", "1", "B", "2"));
|
||||
assertEquals(createMap("A", "1", "B", "2"), message.getHeaders());
|
||||
}
|
||||
|
||||
@Test(expected=UnsupportedOperationException.class)
|
||||
public void shouldReturnAllHeadersUnmodifiable() {
|
||||
Message message = new Message("test" , createMap("A", "1", "B", "2"));
|
||||
CamelMessage message = message("test" , createMap("A", "1", "B", "2"));
|
||||
message.getHeaders().put("x", "y");
|
||||
}
|
||||
|
||||
@Test public void shouldTransformBodyAndPreserveHeaders() {
|
||||
assertEquals(
|
||||
new Message("ab", createMap("A", "1")),
|
||||
new Message("a" , createMap("A", "1")).transformBody((Function<String, Object>) new TestTransformer()));
|
||||
message("ab", createMap("A", "1")),
|
||||
message("a" , createMap("A", "1")).mapBody(new TestTransformer()));
|
||||
}
|
||||
|
||||
@Test public void shouldConvertBodyAndPreserveHeaders() {
|
||||
assertEquals(
|
||||
new Message("1.4", createMap("A", "1")),
|
||||
new Message(1.4 , createMap("A", "1")).setBodyAs(String.class));
|
||||
message("1.4", createMap("A", "1")),
|
||||
message(1.4 , createMap("A", "1")).withBodyAs(String.class,camel.context()));
|
||||
}
|
||||
|
||||
@Test public void shouldSetBodyAndPreserveHeaders() {
|
||||
assertEquals(
|
||||
new Message("test2" , createMap("A", "1")),
|
||||
new Message("test1" , createMap("A", "1")).setBody("test2"));
|
||||
message("test2" , createMap("A", "1")),
|
||||
message("test1" , createMap("A", "1")).withBody("test2"));
|
||||
}
|
||||
|
||||
@Test public void shouldSetHeadersAndPreserveBody() {
|
||||
assertEquals(
|
||||
new Message("test1" , createMap("C", "3")),
|
||||
new Message("test1" , createMap("A", "1")).setHeaders(createMap("C", "3")));
|
||||
message("test1" , createMap("C", "3")),
|
||||
message("test1" , createMap("A", "1")).withHeaders(createMap("C", "3")));
|
||||
}
|
||||
|
||||
@Test public void shouldAddHeaderAndPreserveBodyAndHeaders() {
|
||||
assertEquals(
|
||||
new Message("test1" , createMap("A", "1", "B", "2")),
|
||||
new Message("test1" , createMap("A", "1")).addHeader("B", "2"));
|
||||
message("test1" , createMap("A", "1", "B", "2")),
|
||||
message("test1" , createMap("A", "1")).addHeader("B", "2"));
|
||||
}
|
||||
|
||||
@Test public void shouldAddHeadersAndPreserveBodyAndHeaders() {
|
||||
assertEquals(
|
||||
new Message("test1" , createMap("A", "1", "B", "2")),
|
||||
new Message("test1" , createMap("A", "1")).addHeaders(createMap("B", "2")));
|
||||
message("test1" , createMap("A", "1", "B", "2")),
|
||||
message("test1" , createMap("A", "1")).addHeaders(createMap("B", "2")));
|
||||
}
|
||||
|
||||
@Test public void shouldRemoveHeadersAndPreserveBodyAndRemainingHeaders() {
|
||||
assertEquals(
|
||||
new Message("test1" , createMap("A", "1")),
|
||||
new Message("test1" , createMap("A", "1", "B", "2")).removeHeader("B"));
|
||||
message("test1" , createMap("A", "1")),
|
||||
message("test1" , createMap("A", "1", "B", "2")).withoutHeader("B"));
|
||||
}
|
||||
|
||||
private static Set<String> createSet(String... entries) {
|
||||
|
|
@ -120,7 +137,7 @@ public class MessageJavaTestBase {
|
|||
return map;
|
||||
}
|
||||
|
||||
private static class TestTransformer implements Function<String, Object> {
|
||||
private static class TestTransformer implements Function<String, String> {
|
||||
public String apply(String param) {
|
||||
return param + "b";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,15 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camel;
|
||||
|
||||
import akka.camel.javaapi.UntypedConsumerActor;
|
||||
import akka.util.Duration;
|
||||
import org.apache.camel.builder.Builder;
|
||||
import org.apache.camel.model.ProcessorDefinition;
|
||||
import org.apache.camel.model.RouteDefinition;
|
||||
import scala.Option;
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
|
|
@ -13,22 +20,28 @@ public class SampleErrorHandlingConsumer extends UntypedConsumerActor {
|
|||
return "direct:error-handler-test-java";
|
||||
}
|
||||
|
||||
public boolean isBlocking() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void preStart() {
|
||||
onRouteDefinition(new RouteDefinitionHandler() {
|
||||
public ProcessorDefinition<?> onRouteDefinition(RouteDefinition rd) {
|
||||
@Override
|
||||
//TODO write test confirming this gets called in java
|
||||
public ProcessorDefinition onRouteDefinition(RouteDefinition rd) {
|
||||
return rd.onException(Exception.class).handled(true).transform(Builder.exceptionMessage()).end();
|
||||
}
|
||||
});
|
||||
|
||||
@Override
|
||||
public Duration replyTimeout(){
|
||||
return Duration.create(1, "second");
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void onReceive(Object message) throws Exception {
|
||||
Message msg = (Message)message;
|
||||
String body = msg.getBodyAs(String.class);
|
||||
CamelMessage msg = (CamelMessage) message;
|
||||
String body = msg.getBodyAs(String.class,this.getCamelContext());
|
||||
throw new Exception(String.format("error: %s", body));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preRestart(Throwable reason, Option<Object> message){
|
||||
getSender().tell(new Failure(reason));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camel;
|
||||
|
||||
import akka.actor.UntypedActor;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camel;
|
||||
|
||||
import akka.camel.UntypedConsumerActor;
|
||||
import akka.camel.javaapi.UntypedConsumerActor;
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
|
|
@ -12,10 +16,10 @@ public class SampleUntypedConsumer extends UntypedConsumerActor {
|
|||
}
|
||||
|
||||
public void onReceive(Object message) {
|
||||
Message msg = (Message)message;
|
||||
String body = msg.getBodyAs(String.class);
|
||||
String header = msg.getHeaderAs("test", String.class);
|
||||
sender.tell(String.format("%s %s", body, header));
|
||||
CamelMessage msg = (CamelMessage)message;
|
||||
String body = msg.getBodyAs(String.class, getCamelContext());
|
||||
String header = msg.getHeaderAs("test", String.class,getCamelContext());
|
||||
sender().tell(String.format("%s %s", body, header));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +1,10 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camel;
|
||||
|
||||
import akka.camel.javaapi.UntypedProducerActor;
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
|
|
@ -11,8 +16,8 @@ public class SampleUntypedForwardingProducer extends UntypedProducerActor {
|
|||
|
||||
@Override
|
||||
public void onReceiveAfterProduce(Object message) {
|
||||
Message msg = (Message)message;
|
||||
String body = msg.getBodyAs(String.class);
|
||||
CamelContextManager.getMandatoryTemplate().sendBody("direct:forward-test-1", body);
|
||||
CamelMessage msg = (CamelMessage)message;
|
||||
String body = msg.getBodyAs(String.class,getCamelContext());
|
||||
getProducerTemplate().sendBody("direct:forward-test-1", body);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camel;
|
||||
|
||||
import akka.camel.javaapi.UntypedProducerActor;
|
||||
|
||||
/**
|
||||
* @author Martin Krasser
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration scan="false" debug="false">
|
||||
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>[%4p] [%d{ISO8601}] [%t] %c{1}: %m%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
<root level="OFF">
|
||||
<appender-ref ref="stdout"/>
|
||||
</root>
|
||||
</configuration>
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camel
|
||||
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
import akka.util.duration._
|
||||
import org.apache.camel.ProducerTemplate
|
||||
import akka.actor._
|
||||
import akka.util.Timeout
|
||||
import TestSupport._
|
||||
import org.scalatest.WordSpec
|
||||
import akka.testkit.TestLatch
|
||||
import akka.dispatch.Await
|
||||
|
||||
class ActivationIntegrationTest extends WordSpec with MustMatchers with SharedCamelSystem {
|
||||
implicit val timeout = Timeout(10 seconds)
|
||||
def template: ProducerTemplate = camel.template
|
||||
|
||||
def testActorWithEndpoint(uri: String): ActorRef = { system.actorOf(Props(new TestConsumer(uri))) }
|
||||
|
||||
"ActivationAware must be notified when endpoint is activated" in {
|
||||
val actor = testActorWithEndpoint("direct:actor-1")
|
||||
try {
|
||||
camel.awaitActivation(actor, 1 second)
|
||||
} catch {
|
||||
case e: ActivationTimeoutException ⇒ fail("Failed to get notification within 1 second")
|
||||
}
|
||||
|
||||
template.requestBody("direct:actor-1", "test") must be("received test")
|
||||
}
|
||||
|
||||
"ActivationAware must be notified when endpoint is de-activated" in {
|
||||
val latch = TestLatch()
|
||||
val actor = start(new Consumer {
|
||||
def endpointUri = "direct:a3"
|
||||
def receive = { case _ ⇒ {} }
|
||||
|
||||
override def postStop() = {
|
||||
super.postStop()
|
||||
latch.countDown()
|
||||
}
|
||||
})
|
||||
camel.awaitActivation(actor, 1 second)
|
||||
|
||||
system.stop(actor)
|
||||
camel.awaitDeactivation(actor, 1 second)
|
||||
Await.ready(latch, 1 second)
|
||||
}
|
||||
|
||||
"ActivationAware must time out when waiting for endpoint de-activation for too long" in {
|
||||
val actor = start(new TestConsumer("direct:a5"))
|
||||
camel.awaitActivation(actor, 1 second)
|
||||
intercept[DeActivationTimeoutException] {
|
||||
camel.awaitDeactivation(actor, 1 millis)
|
||||
}
|
||||
}
|
||||
|
||||
"awaitActivation must fail if notification timeout is too short and activation is not complete yet" in {
|
||||
val actor = testActorWithEndpoint("direct:actor-4")
|
||||
intercept[ActivationTimeoutException] {
|
||||
camel.awaitActivation(actor, 0 seconds)
|
||||
}
|
||||
}
|
||||
|
||||
class TestConsumer(uri: String) extends Consumer {
|
||||
def endpointUri = uri
|
||||
override def receive = {
|
||||
case msg: CamelMessage ⇒ sender ! "received " + msg.body
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -1,95 +1,96 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camel
|
||||
|
||||
import org.apache.camel.impl.{ DefaultCamelContext, DefaultExchange }
|
||||
import org.apache.camel.ExchangePattern
|
||||
import org.junit.Test
|
||||
import org.scalatest.junit.JUnitSuite
|
||||
import internal.CamelExchangeAdapter
|
||||
import org.apache.camel.impl.DefaultExchange
|
||||
import org.apache.camel.{ Exchange, ExchangePattern }
|
||||
import akka.camel.TestSupport.{ SharedCamelSystem, MessageSugar }
|
||||
import org.scalatest.FunSuite
|
||||
|
||||
class CamelExchangeAdapterTest extends JUnitSuite {
|
||||
import CamelMessageConversion.toExchangeAdapter
|
||||
class CamelExchangeAdapterTest extends FunSuite with SharedCamelSystem with MessageSugar {
|
||||
|
||||
@Test
|
||||
def shouldSetInMessageFromRequestMessage = {
|
||||
val e1 = sampleInOnly.fromRequestMessage(Message("x"))
|
||||
//TODO: Get rid of implicit.
|
||||
// It is here, as was easier to add this implicit than to rewrite the whole test...
|
||||
implicit def exchangeToAdapter(e: Exchange) = new CamelExchangeAdapter(e)
|
||||
|
||||
test("mustSetInMessageFromRequestMessage") {
|
||||
val e1 = sampleInOnly
|
||||
e1.setRequest(Message("x"))
|
||||
assert(e1.getIn.getBody === "x")
|
||||
val e2 = sampleInOut.fromRequestMessage(Message("y"))
|
||||
val e2 = sampleInOut
|
||||
e2.setRequest(Message("y"))
|
||||
assert(e2.getIn.getBody === "y")
|
||||
}
|
||||
|
||||
@Test
|
||||
def shouldSetOutMessageFromResponseMessage = {
|
||||
val e1 = sampleInOut.fromResponseMessage(Message("y"))
|
||||
test("mustSetOutMessageFromResponseMessage") {
|
||||
val e1 = sampleInOut
|
||||
e1.setResponse(Message("y"))
|
||||
assert(e1.getOut.getBody === "y")
|
||||
}
|
||||
|
||||
@Test
|
||||
def shouldSetInMessageFromResponseMessage = {
|
||||
val e1 = sampleInOnly.fromResponseMessage(Message("x"))
|
||||
test("mustSetInMessageFromResponseMessage") {
|
||||
val e1 = sampleInOnly
|
||||
e1.setResponse(Message("x"))
|
||||
assert(e1.getIn.getBody === "x")
|
||||
}
|
||||
|
||||
@Test
|
||||
def shouldSetExceptionFromFailureMessage = {
|
||||
val e1 = sampleInOnly.fromFailureMessage(Failure(new Exception("test1")))
|
||||
test("mustSetExceptionFromFailureMessage") {
|
||||
val e1 = sampleInOnly
|
||||
e1.setFailure(Failure(new Exception("test1")))
|
||||
assert(e1.getException.getMessage === "test1")
|
||||
val e2 = sampleInOut.fromFailureMessage(Failure(new Exception("test2")))
|
||||
val e2 = sampleInOut
|
||||
e2.setFailure(Failure(new Exception("test2")))
|
||||
assert(e2.getException.getMessage === "test2")
|
||||
}
|
||||
|
||||
@Test
|
||||
def shouldCreateRequestMessageFromInMessage = {
|
||||
test("mustCreateRequestMessageFromInMessage") {
|
||||
val m = sampleInOnly.toRequestMessage
|
||||
assert(m === Message("test-in", Map("key-in" -> "val-in")))
|
||||
}
|
||||
|
||||
@Test
|
||||
def shouldCreateResponseMessageFromInMessage = {
|
||||
test("mustCreateResponseMessageFromInMessage") {
|
||||
val m = sampleInOnly.toResponseMessage
|
||||
assert(m === Message("test-in", Map("key-in" -> "val-in")))
|
||||
}
|
||||
|
||||
@Test
|
||||
def shouldCreateResponseMessageFromOutMessage = {
|
||||
test("mustCreateResponseMessageFromOutMessage") {
|
||||
val m = sampleInOut.toResponseMessage
|
||||
assert(m === Message("test-out", Map("key-out" -> "val-out")))
|
||||
}
|
||||
|
||||
@Test
|
||||
def shouldCreateFailureMessageFromExceptionAndInMessage = {
|
||||
test("mustCreateFailureMessageFromExceptionAndInMessage") {
|
||||
val e1 = sampleInOnly
|
||||
e1.setException(new Exception("test1"))
|
||||
assert(e1.toFailureMessage.cause.getMessage === "test1")
|
||||
assert(e1.toFailureMessage.headers("key-in") === "val-in")
|
||||
}
|
||||
|
||||
@Test
|
||||
def shouldCreateFailureMessageFromExceptionAndOutMessage = {
|
||||
test("mustCreateFailureMessageFromExceptionAndOutMessage") {
|
||||
val e1 = sampleInOut
|
||||
e1.setException(new Exception("test2"))
|
||||
assert(e1.toFailureMessage.cause.getMessage === "test2")
|
||||
assert(e1.toFailureMessage.headers("key-out") === "val-out")
|
||||
}
|
||||
|
||||
@Test
|
||||
def shouldCreateRequestMessageFromInMessageWithAdditionalHeader = {
|
||||
test("mustCreateRequestMessageFromInMessageWithAdditionalHeader") {
|
||||
val m = sampleInOnly.toRequestMessage(Map("x" -> "y"))
|
||||
assert(m === Message("test-in", Map("key-in" -> "val-in", "x" -> "y")))
|
||||
}
|
||||
|
||||
@Test
|
||||
def shouldCreateResponseMessageFromInMessageWithAdditionalHeader = {
|
||||
test("mustCreateResponseMessageFromInMessageWithAdditionalHeader") {
|
||||
val m = sampleInOnly.toResponseMessage(Map("x" -> "y"))
|
||||
assert(m === Message("test-in", Map("key-in" -> "val-in", "x" -> "y")))
|
||||
}
|
||||
|
||||
@Test
|
||||
def shouldCreateResponseMessageFromOutMessageWithAdditionalHeader = {
|
||||
test("mustCreateResponseMessageFromOutMessageWithAdditionalHeader") {
|
||||
val m = sampleInOut.toResponseMessage(Map("x" -> "y"))
|
||||
assert(m === Message("test-out", Map("key-out" -> "val-out", "x" -> "y")))
|
||||
}
|
||||
|
||||
@Test
|
||||
def shouldCreateFailureMessageFromExceptionAndInMessageWithAdditionalHeader = {
|
||||
test("mustCreateFailureMessageFromExceptionAndInMessageWithAdditionalHeader") {
|
||||
val e1 = sampleInOnly
|
||||
e1.setException(new Exception("test1"))
|
||||
assert(e1.toFailureMessage.cause.getMessage === "test1")
|
||||
|
|
@ -98,8 +99,7 @@ class CamelExchangeAdapterTest extends JUnitSuite {
|
|||
assert(headers("x") === "y")
|
||||
}
|
||||
|
||||
@Test
|
||||
def shouldCreateFailureMessageFromExceptionAndOutMessageWithAdditionalHeader = {
|
||||
test("mustCreateFailureMessageFromExceptionAndOutMessageWithAdditionalHeader") {
|
||||
val e1 = sampleInOut
|
||||
e1.setException(new Exception("test2"))
|
||||
assert(e1.toFailureMessage.cause.getMessage === "test2")
|
||||
|
|
@ -112,7 +112,7 @@ class CamelExchangeAdapterTest extends JUnitSuite {
|
|||
private def sampleInOut = sampleExchange(ExchangePattern.InOut)
|
||||
|
||||
private def sampleExchange(pattern: ExchangePattern) = {
|
||||
val exchange = new DefaultExchange(new DefaultCamelContext)
|
||||
val exchange = new DefaultExchange(camel.context)
|
||||
exchange.getIn.setBody("test-in")
|
||||
exchange.getOut.setBody("test-out")
|
||||
exchange.getIn.setHeader("key-in", "val-in")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
46
akka-camel/src/test/scala/akka/camel/CamelMessageTest.scala
Normal file
46
akka-camel/src/test/scala/akka/camel/CamelMessageTest.scala
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camel
|
||||
|
||||
import org.apache.camel.impl.{ DefaultExchange, DefaultMessage }
|
||||
import akka.camel.TestSupport.SharedCamelSystem
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
import org.scalatest.WordSpec
|
||||
|
||||
//TODO merge it with MessageScalaTest
|
||||
class CamelMessageTest extends MustMatchers with WordSpec with SharedCamelSystem {
|
||||
|
||||
"CamelMessage" must {
|
||||
|
||||
"overwrite body and add header" in {
|
||||
val msg = sampleMessage
|
||||
CamelMessage("blah", Map("key" -> "baz")).copyContentTo(msg)
|
||||
assert(msg.getBody === "blah")
|
||||
assert(msg.getHeader("foo") === "bar")
|
||||
assert(msg.getHeader("key") === "baz")
|
||||
}
|
||||
|
||||
"create message with body and header" in {
|
||||
val m = CamelMessage.from(sampleMessage)
|
||||
assert(m.body === "test")
|
||||
assert(m.headers("foo") === "bar")
|
||||
}
|
||||
|
||||
"create message with body and header and custom header" in {
|
||||
val m = CamelMessage.from(sampleMessage, Map("key" -> "baz"))
|
||||
assert(m.body === "test")
|
||||
assert(m.headers("foo") === "bar")
|
||||
assert(m.headers("key") === "baz")
|
||||
}
|
||||
}
|
||||
|
||||
private[camel] def sampleMessage = {
|
||||
val message = new DefaultMessage
|
||||
message.setBody("test")
|
||||
message.setHeader("foo", "bar")
|
||||
message.setExchange(new DefaultExchange(camel.context))
|
||||
message
|
||||
}
|
||||
}
|
||||
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,171 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camel
|
||||
|
||||
import akka.actor._
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
import akka.util.duration._
|
||||
import TestSupport._
|
||||
import org.scalatest.WordSpec
|
||||
import org.apache.camel.model.RouteDefinition
|
||||
import org.apache.camel.builder.Builder
|
||||
import org.apache.camel.{ FailedToCreateRouteException, CamelExecutionException }
|
||||
import java.util.concurrent.{ ExecutionException, TimeUnit, TimeoutException }
|
||||
import akka.testkit.TestLatch
|
||||
import akka.dispatch.Await
|
||||
|
||||
class ConsumerIntegrationTest extends WordSpec with MustMatchers with NonSharedCamelSystem {
|
||||
private val defaultTimeout = 10
|
||||
"Consumer must throw FailedToCreateRouteException, while awaiting activation, if endpoint is invalid" in {
|
||||
val actorRef = system.actorOf(Props(new TestActor(uri = "some invalid uri")))
|
||||
|
||||
intercept[FailedToCreateRouteException] {
|
||||
camel.awaitActivation(actorRef, timeout = defaultTimeout seconds)
|
||||
}
|
||||
}
|
||||
|
||||
"Consumer must support in-out messaging" in {
|
||||
start(new Consumer {
|
||||
def endpointUri = "direct:a1"
|
||||
def receive = {
|
||||
case m: CamelMessage ⇒ sender ! "received " + m.bodyAs[String]
|
||||
}
|
||||
})
|
||||
camel.sendTo("direct:a1", msg = "some message") must be("received some message")
|
||||
}
|
||||
|
||||
"Consumer must time-out if consumer is slow" in {
|
||||
val SHORT_TIMEOUT = 10 millis
|
||||
val LONG_WAIT = 200 millis
|
||||
|
||||
start(new Consumer {
|
||||
override def replyTimeout = SHORT_TIMEOUT
|
||||
|
||||
def endpointUri = "direct:a3"
|
||||
def receive = { case _ ⇒ { Thread.sleep(LONG_WAIT.toMillis); sender ! "done" } }
|
||||
})
|
||||
|
||||
val exception = intercept[CamelExecutionException] {
|
||||
camel.sendTo("direct:a3", msg = "some msg 3")
|
||||
}
|
||||
exception.getCause.getClass must be(classOf[TimeoutException])
|
||||
}
|
||||
|
||||
"Consumer must process messages even after actor restart" in {
|
||||
val restarted = TestLatch()
|
||||
val consumer = start(new Consumer {
|
||||
def endpointUri = "direct:a2"
|
||||
|
||||
def receive = {
|
||||
case "throw" ⇒ throw new Exception
|
||||
case m: CamelMessage ⇒ sender ! "received " + m.bodyAs[String]
|
||||
}
|
||||
|
||||
override def postRestart(reason: Throwable) {
|
||||
restarted.countDown()
|
||||
}
|
||||
})
|
||||
consumer ! "throw"
|
||||
Await.ready(restarted, defaultTimeout seconds)
|
||||
|
||||
val response = camel.sendTo("direct:a2", msg = "xyz")
|
||||
response must be("received xyz")
|
||||
}
|
||||
|
||||
"Consumer must unregister itself when stopped" in {
|
||||
val consumer = start(new TestActor())
|
||||
camel.awaitActivation(consumer, defaultTimeout seconds)
|
||||
|
||||
camel.routeCount must be > (0)
|
||||
|
||||
system.stop(consumer)
|
||||
camel.awaitDeactivation(consumer, defaultTimeout seconds)
|
||||
|
||||
camel.routeCount must be(0)
|
||||
}
|
||||
|
||||
"Error passing consumer supports error handling through route modification" in {
|
||||
start(new ErrorThrowingConsumer("direct:error-handler-test") with ErrorPassing {
|
||||
override def onRouteDefinition(rd: RouteDefinition) = {
|
||||
rd.onException(classOf[Exception]).handled(true).transform(Builder.exceptionMessage).end
|
||||
}
|
||||
})
|
||||
camel.sendTo("direct:error-handler-test", msg = "hello") must be("error: hello")
|
||||
}
|
||||
|
||||
"Error passing consumer supports redelivery through route modification" in {
|
||||
start(new FailingOnceConsumer("direct:failing-once-concumer") with ErrorPassing {
|
||||
override def onRouteDefinition(rd: RouteDefinition) = {
|
||||
rd.onException(classOf[Exception]).maximumRedeliveries(1).end
|
||||
}
|
||||
})
|
||||
camel.sendTo("direct:failing-once-concumer", msg = "hello") must be("accepted: hello")
|
||||
}
|
||||
|
||||
"Consumer supports manual Ack" in {
|
||||
start(new ManualAckConsumer() {
|
||||
def endpointUri = "direct:manual-ack"
|
||||
def receive = { case _ ⇒ sender ! Ack }
|
||||
})
|
||||
camel.template.asyncSendBody("direct:manual-ack", "some message").get(defaultTimeout, TimeUnit.SECONDS) must be(null) //should not timeout
|
||||
}
|
||||
|
||||
"Consumer handles manual Ack failure" in {
|
||||
val someException = new Exception("e1")
|
||||
start(new ManualAckConsumer() {
|
||||
def endpointUri = "direct:manual-ack"
|
||||
def receive = { case _ ⇒ sender ! Failure(someException) }
|
||||
})
|
||||
|
||||
intercept[ExecutionException] {
|
||||
camel.template.asyncSendBody("direct:manual-ack", "some message").get(defaultTimeout, TimeUnit.SECONDS)
|
||||
}.getCause.getCause must be(someException)
|
||||
}
|
||||
|
||||
"Consumer should time-out, if manual Ack not received within replyTimeout and should give a human readable error message" in {
|
||||
start(new ManualAckConsumer() {
|
||||
override def replyTimeout = 10 millis
|
||||
def endpointUri = "direct:manual-ack"
|
||||
def receive = { case _ ⇒ }
|
||||
})
|
||||
|
||||
intercept[ExecutionException] {
|
||||
camel.template.asyncSendBody("direct:manual-ack", "some message").get(defaultTimeout, TimeUnit.SECONDS)
|
||||
}.getCause.getCause.getMessage must include("Failed to get Ack")
|
||||
}
|
||||
}
|
||||
|
||||
class ErrorThrowingConsumer(override val endpointUri: String) extends Consumer {
|
||||
def receive = {
|
||||
case msg: CamelMessage ⇒ throw new Exception("error: %s" format msg.body)
|
||||
}
|
||||
}
|
||||
|
||||
class FailingOnceConsumer(override val endpointUri: String) extends Consumer {
|
||||
|
||||
def receive = {
|
||||
case msg: CamelMessage ⇒
|
||||
if (msg.headerAs[Boolean]("CamelRedelivered").getOrElse(false))
|
||||
sender ! ("accepted: %s" format msg.body)
|
||||
else
|
||||
throw new Exception("rejected: %s" format msg.body)
|
||||
}
|
||||
}
|
||||
|
||||
class TestActor(uri: String = "file://target/abcde") extends Consumer {
|
||||
def endpointUri = uri
|
||||
def receive = { case _ ⇒ /* do nothing */ }
|
||||
}
|
||||
|
||||
trait ErrorPassing {
|
||||
self: Actor ⇒
|
||||
final override def preRestart(reason: Throwable, message: Option[Any]) {
|
||||
sender ! Failure(reason)
|
||||
}
|
||||
}
|
||||
|
||||
trait ManualAckConsumer extends Consumer {
|
||||
override def autoack = false
|
||||
}
|
||||
|
|
@ -1,3 +1,7 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camel
|
||||
|
||||
import org.scalatest.junit.JUnitSuite
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
||||
59
akka-camel/src/test/scala/akka/camel/DefaultCamelTest.scala
Normal file
59
akka-camel/src/test/scala/akka/camel/DefaultCamelTest.scala
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
/**
|
||||
* Copyright (C) 2009when2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camel
|
||||
|
||||
import akka.camel.TestSupport.SharedCamelSystem
|
||||
import internal.DefaultCamel
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
import org.scalatest.mock.MockitoSugar
|
||||
import akka.actor.ActorSystem
|
||||
import org.apache.camel.{ CamelContext, ProducerTemplate }
|
||||
import org.scalatest.WordSpec
|
||||
import akka.event.LoggingAdapter
|
||||
|
||||
class DefaultCamelTest extends WordSpec with SharedCamelSystem with MustMatchers with MockitoSugar {
|
||||
|
||||
import org.mockito.Mockito.{ when, verify }
|
||||
|
||||
def camelWitMocks = new DefaultCamel(mock[ActorSystem]) {
|
||||
override val log = mock[LoggingAdapter]
|
||||
override lazy val template = mock[ProducerTemplate]
|
||||
override lazy val context = mock[CamelContext]
|
||||
}
|
||||
|
||||
"during shutdown, when both context and template fail to shutdown" when {
|
||||
val camel = camelWitMocks
|
||||
|
||||
when(camel.context.stop()) thenThrow new RuntimeException("context")
|
||||
when(camel.template.stop()) thenThrow new RuntimeException("template")
|
||||
val exception = intercept[RuntimeException] {
|
||||
camel.shutdown()
|
||||
}
|
||||
|
||||
"throws exception thrown by context.stop()" in {
|
||||
exception.getMessage() must be("context");
|
||||
}
|
||||
|
||||
"tries to stop both template and context" in {
|
||||
verify(camel.template).stop()
|
||||
verify(camel.context).stop()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
"during start, if template fails to start, it will stop the context" in {
|
||||
val camel = camelWitMocks
|
||||
|
||||
when(camel.template.start()) thenThrow new RuntimeException
|
||||
|
||||
intercept[RuntimeException] {
|
||||
camel.start
|
||||
}
|
||||
|
||||
verify(camel.context).stop()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,3 +1,7 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camel
|
||||
|
||||
import org.scalatest.junit.JUnitSuite
|
||||
|
|
|
|||
|
|
@ -1,94 +1,78 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camel
|
||||
|
||||
import java.io.InputStream
|
||||
|
||||
import org.apache.camel.NoTypeConversionAvailableException
|
||||
import org.junit.Assert._
|
||||
import org.junit.Test
|
||||
import akka.camel.TestSupport.{ SharedCamelSystem, MessageSugar }
|
||||
import org.scalatest.FunSuite
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
|
||||
import org.scalatest.BeforeAndAfterAll
|
||||
import org.scalatest.junit.JUnitSuite
|
||||
|
||||
class MessageScalaTest extends JUnitSuite with BeforeAndAfterAll {
|
||||
override protected def beforeAll = CamelContextManager.init
|
||||
|
||||
@Test
|
||||
def shouldConvertDoubleBodyToString = {
|
||||
assertEquals("1.4", Message(1.4).bodyAs[String])
|
||||
class MessageScalaTest extends FunSuite with MustMatchers with SharedCamelSystem with MessageSugar {
|
||||
implicit def camelContext = camel.context
|
||||
test("mustConvertDoubleBodyToString") {
|
||||
Message(1.4).bodyAs[String] must be("1.4")
|
||||
}
|
||||
|
||||
@Test
|
||||
def shouldThrowExceptionWhenConvertingDoubleBodyToInputStream {
|
||||
test("mustThrowExceptionWhenConvertingDoubleBodyToInputStream") {
|
||||
intercept[NoTypeConversionAvailableException] {
|
||||
Message(1.4).bodyAs[InputStream]
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
def shouldReturnDoubleHeader = {
|
||||
test("mustReturnDoubleHeader") {
|
||||
val message = Message("test", Map("test" -> 1.4))
|
||||
assertEquals(1.4, message.header("test"))
|
||||
message.header("test").get must be(1.4)
|
||||
}
|
||||
|
||||
@Test
|
||||
def shouldConvertDoubleHeaderToString = {
|
||||
test("mustConvertDoubleHeaderToString") {
|
||||
val message = Message("test", Map("test" -> 1.4))
|
||||
assertEquals("1.4", message.headerAs[String]("test"))
|
||||
message.headerAs[String]("test").get must be("1.4")
|
||||
}
|
||||
|
||||
@Test
|
||||
def shouldReturnSubsetOfHeaders = {
|
||||
test("mustReturnSubsetOfHeaders") {
|
||||
val message = Message("test", Map("A" -> "1", "B" -> "2"))
|
||||
assertEquals(Map("B" -> "2"), message.headers(Set("B")))
|
||||
message.headers(Set("B")) must be(Map("B" -> "2"))
|
||||
}
|
||||
|
||||
@Test
|
||||
def shouldTransformBodyAndPreserveHeaders = {
|
||||
assertEquals(
|
||||
Message("ab", Map("A" -> "1")),
|
||||
Message("a", Map("A" -> "1")).transformBody((body: String) ⇒ body + "b"))
|
||||
test("mustTransformBodyAndPreserveHeaders") {
|
||||
Message("a", Map("A" -> "1")).mapBody((body: String) ⇒ body + "b") must be(Message("ab", Map("A" -> "1")))
|
||||
}
|
||||
|
||||
@Test
|
||||
def shouldConvertBodyAndPreserveHeaders = {
|
||||
assertEquals(
|
||||
Message("1.4", Map("A" -> "1")),
|
||||
Message(1.4, Map("A" -> "1")).setBodyAs[String])
|
||||
test("mustConvertBodyAndPreserveHeaders") {
|
||||
Message(1.4, Map("A" -> "1")).withBodyAs[String] must be(Message("1.4", Map("A" -> "1")))
|
||||
}
|
||||
|
||||
@Test
|
||||
def shouldSetBodyAndPreserveHeaders = {
|
||||
assertEquals(
|
||||
Message("test2", Map("A" -> "1")),
|
||||
Message("test1", Map("A" -> "1")).setBody("test2"))
|
||||
}
|
||||
|
||||
@Test
|
||||
def shouldSetHeadersAndPreserveBody = {
|
||||
assertEquals(
|
||||
Message("test1", Map("C" -> "3")),
|
||||
Message("test1", Map("A" -> "1")).setHeaders(Map("C" -> "3")))
|
||||
test("mustSetBodyAndPreserveHeaders") {
|
||||
Message("test1", Map("A" -> "1")).withBody("test2") must be(
|
||||
Message("test2", Map("A" -> "1")))
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
def shouldAddHeaderAndPreserveBodyAndHeaders = {
|
||||
assertEquals(
|
||||
Message("test1", Map("A" -> "1", "B" -> "2")),
|
||||
Message("test1", Map("A" -> "1")).addHeader("B" -> "2"))
|
||||
test("mustSetHeadersAndPreserveBody") {
|
||||
Message("test1", Map("A" -> "1")).withHeaders(Map("C" -> "3")) must be(
|
||||
Message("test1", Map("C" -> "3")))
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
def shouldAddHeadersAndPreserveBodyAndHeaders = {
|
||||
assertEquals(
|
||||
Message("test1", Map("A" -> "1", "B" -> "2")),
|
||||
Message("test1", Map("A" -> "1")).addHeaders(Map("B" -> "2")))
|
||||
test("mustAddHeaderAndPreserveBodyAndHeaders") {
|
||||
Message("test1", Map("A" -> "1")).addHeader("B" -> "2") must be(
|
||||
Message("test1", Map("A" -> "1", "B" -> "2")))
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
def shouldRemoveHeadersAndPreserveBodyAndRemainingHeaders = {
|
||||
assertEquals(
|
||||
Message("test1", Map("A" -> "1")),
|
||||
Message("test1", Map("A" -> "1", "B" -> "2")).removeHeader("B"))
|
||||
test("mustAddHeadersAndPreserveBodyAndHeaders") {
|
||||
Message("test1", Map("A" -> "1")).addHeaders(Map("B" -> "2")) must be(
|
||||
Message("test1", Map("A" -> "1", "B" -> "2")))
|
||||
|
||||
}
|
||||
|
||||
test("mustRemoveHeadersAndPreserveBodyAndRemainingHeaders") {
|
||||
Message("test1", Map("A" -> "1", "B" -> "2")).withoutHeader("B") must be(
|
||||
Message("test1", Map("A" -> "1")))
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,250 +1,282 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camel
|
||||
|
||||
import org.apache.camel.{ Exchange, Processor }
|
||||
import org.apache.camel.builder.RouteBuilder
|
||||
import org.apache.camel.component.mock.MockEndpoint
|
||||
import org.scalatest.{ GivenWhenThen, BeforeAndAfterEach, BeforeAndAfterAll, FeatureSpec }
|
||||
import akka.actor._
|
||||
import akka.pattern._
|
||||
import akka.dispatch.Await
|
||||
import akka.util.duration._
|
||||
import akka.camel.TestSupport.SharedCamelSystem
|
||||
import org.scalatest._
|
||||
|
||||
import akka.actor.Actor._
|
||||
import akka.actor.{ ActorRef, Actor }
|
||||
/**
|
||||
* Tests the features of the Camel Producer.
|
||||
*/
|
||||
class ProducerFeatureTest extends WordSpec with BeforeAndAfterAll with BeforeAndAfterEach with SharedCamelSystem with GivenWhenThen {
|
||||
|
||||
class ProducerFeatureTest extends FeatureSpec with BeforeAndAfterAll with BeforeAndAfterEach with GivenWhenThen {
|
||||
import ProducerFeatureTest._
|
||||
|
||||
override protected def beforeAll = {
|
||||
Actor.registry.local.shutdownAll
|
||||
CamelContextManager.init
|
||||
CamelContextManager.mandatoryContext.addRoutes(new TestRoute)
|
||||
CamelContextManager.start
|
||||
}
|
||||
val camelContext = camel.context
|
||||
// to make testing equality of messages easier, otherwise the breadcrumb shows up in the result.
|
||||
camelContext.setUseBreadcrumb(false)
|
||||
|
||||
override protected def afterAll = {
|
||||
CamelContextManager.stop
|
||||
Actor.registry.local.shutdownAll
|
||||
}
|
||||
val timeout = 1 second
|
||||
|
||||
override protected def afterEach = {
|
||||
mockEndpoint.reset
|
||||
}
|
||||
override protected def beforeAll { camelContext.addRoutes(new TestRoute(system)) }
|
||||
|
||||
feature("Produce a message to a sync Camel route") {
|
||||
override protected def afterEach { mockEndpoint.reset() }
|
||||
|
||||
scenario("produce message and receive normal response") {
|
||||
"A Producer on a sync Camel route" must {
|
||||
|
||||
"produce a message and receive normal response" in {
|
||||
given("a registered two-way producer")
|
||||
val producer = actorOf(Props(new TestProducer("direct:producer-test-2", true))
|
||||
|
||||
val producer = system.actorOf(Props(new TestProducer("direct:producer-test-2", true)))
|
||||
when("a test message is sent to the producer with ?")
|
||||
val message = Message("test", Map(Message.MessageExchangeId -> "123"))
|
||||
val result = (producer ? message).get
|
||||
|
||||
then("a normal response should have been returned by the producer")
|
||||
val expected = Message("received TEST", Map(Message.MessageExchangeId -> "123"))
|
||||
assert(result === expected)
|
||||
val message = CamelMessage("test", Map(CamelMessage.MessageExchangeId -> "123"))
|
||||
val future = producer.ask(message)(timeout)
|
||||
then("a normal response must have been returned by the producer")
|
||||
val expected = CamelMessage("received TEST", Map(CamelMessage.MessageExchangeId -> "123"))
|
||||
Await.result(future, timeout) match {
|
||||
case result: CamelMessage ⇒ assert(result === expected)
|
||||
case unexpected ⇒ fail("Actor responded with unexpected message:" + unexpected)
|
||||
}
|
||||
}
|
||||
|
||||
scenario("produce message and receive failure response") {
|
||||
"produce a message and receive failure response" in {
|
||||
given("a registered two-way producer")
|
||||
val producer = actorOf(Props(new TestProducer("direct:producer-test-2"))
|
||||
val producer = system.actorOf(Props(new TestProducer("direct:producer-test-2")))
|
||||
|
||||
when("a test message causing an exception is sent to the producer with ?")
|
||||
val message = Message("fail", Map(Message.MessageExchangeId -> "123"))
|
||||
val result = (producer ? message).as[Failure]
|
||||
|
||||
then("a failure response should have been returned by the producer")
|
||||
val expectedFailureText = result.get.cause.getMessage
|
||||
val expectedHeaders = result.get.headers
|
||||
assert(expectedFailureText === "failure")
|
||||
assert(expectedHeaders === Map(Message.MessageExchangeId -> "123"))
|
||||
}
|
||||
|
||||
scenario("produce message oneway") {
|
||||
given("a registered one-way producer")
|
||||
val producer = actorOf(Props(new TestProducer("direct:producer-test-1", true) with Oneway)
|
||||
|
||||
when("a test message is sent to the producer with !")
|
||||
mockEndpoint.expectedBodiesReceived("TEST")
|
||||
producer ! Message("test")
|
||||
|
||||
then("the test message should have been sent to mock:mock")
|
||||
mockEndpoint.assertIsSatisfied
|
||||
}
|
||||
|
||||
scenario("produce message twoway without sender reference") {
|
||||
given("a registered two-way producer")
|
||||
val producer = actorOf(Props(new TestProducer("direct:producer-test-1"))
|
||||
|
||||
when("a test message is sent to the producer with !")
|
||||
mockEndpoint.expectedBodiesReceived("test")
|
||||
producer ! Message("test")
|
||||
|
||||
then("there should be only a warning that there's no sender reference")
|
||||
mockEndpoint.assertIsSatisfied
|
||||
}
|
||||
}
|
||||
|
||||
feature("Produce a message to an async Camel route") {
|
||||
|
||||
scenario("produce message and receive normal response") {
|
||||
given("a registered two-way producer")
|
||||
val producer = actorOf(Props(new TestProducer("direct:producer-test-3"))
|
||||
|
||||
when("a test message is sent to the producer with ?")
|
||||
val message = Message("test", Map(Message.MessageExchangeId -> "123"))
|
||||
val result = (producer ? message).as[Message].get
|
||||
|
||||
then("a normal response should have been returned by the producer")
|
||||
assert(result.headers(Message.MessageExchangeId) === "123")
|
||||
}
|
||||
|
||||
scenario("produce message and receive failure response") {
|
||||
given("a registered two-way producer")
|
||||
val producer = actorOf(Props(new TestProducer("direct:producer-test-3"))
|
||||
|
||||
when("a test message causing an exception is sent to the producer with ?")
|
||||
val message = Message("fail", Map(Message.MessageExchangeId -> "123"))
|
||||
val result = (producer ? message).as[Failure]
|
||||
|
||||
then("a failure response should have been returned by the producer")
|
||||
val expectedFailureText = result.get.cause.getMessage
|
||||
val expectedHeaders = result.get.headers
|
||||
assert(expectedFailureText === "failure")
|
||||
assert(expectedHeaders === Map(Message.MessageExchangeId -> "123"))
|
||||
}
|
||||
}
|
||||
|
||||
feature("Produce a message to a sync Camel route and then forward the response") {
|
||||
|
||||
scenario("produce message, forward normal response to a replying target actor and receive response") {
|
||||
given("a registered two-way producer configured with a forward target")
|
||||
val target = actorOf(Props[ReplyingForwardTarget]
|
||||
val producer = actorOf(Props(new TestForwarder("direct:producer-test-2", target))
|
||||
|
||||
when("a test message is sent to the producer with ?")
|
||||
val message = Message("test", Map(Message.MessageExchangeId -> "123"))
|
||||
val result = (producer ? message).get
|
||||
|
||||
then("a normal response should have been returned by the forward target")
|
||||
val expected = Message("received test", Map(Message.MessageExchangeId -> "123", "test" -> "result"))
|
||||
assert(result === expected)
|
||||
}
|
||||
|
||||
scenario("produce message, forward failure response to a replying target actor and receive response") {
|
||||
given("a registered two-way producer configured with a forward target")
|
||||
val target = actorOf(Props[ReplyingForwardTarget]
|
||||
val producer = actorOf(Props(new TestForwarder("direct:producer-test-2", target))
|
||||
|
||||
when("a test message causing an exception is sent to the producer with ?")
|
||||
val message = Message("fail", Map(Message.MessageExchangeId -> "123"))
|
||||
val result = (producer ? message).as[Failure].get
|
||||
|
||||
then("a failure response should have been returned by the forward target")
|
||||
val message = CamelMessage("fail", Map(CamelMessage.MessageExchangeId -> "123"))
|
||||
val future = producer.ask(message)(timeout)
|
||||
Await.result(future, timeout) match {
|
||||
case result: Failure ⇒
|
||||
then("a failure response must have been returned by the producer")
|
||||
val expectedFailureText = result.cause.getMessage
|
||||
val expectedHeaders = result.headers
|
||||
assert(expectedFailureText === "failure")
|
||||
assert(expectedHeaders === Map(Message.MessageExchangeId -> "123", "test" -> "failure"))
|
||||
assert(expectedHeaders === Map(CamelMessage.MessageExchangeId -> "123"))
|
||||
case unexpected ⇒ fail("Actor responded with unexpected message:" + unexpected)
|
||||
}
|
||||
}
|
||||
|
||||
scenario("produce message, forward normal response to a producing target actor and produce response to direct:forward-test-1") {
|
||||
given("a registered one-way producer configured with a forward target")
|
||||
val target = actorOf(Props[ProducingForwardTarget]
|
||||
val producer = actorOf(Props(new TestForwarder("direct:producer-test-2", target))
|
||||
"produce a message oneway" in {
|
||||
given("a registered one-way producer")
|
||||
val producer = system.actorOf(Props(new TestProducer("direct:producer-test-1", true) with Oneway))
|
||||
|
||||
when("a test message is sent to the producer with !")
|
||||
mockEndpoint.expectedBodiesReceived("received test")
|
||||
val result = producer.!(Message("test"))(Some(producer))
|
||||
mockEndpoint.expectedBodiesReceived("TEST")
|
||||
producer ! CamelMessage("test", Map())
|
||||
|
||||
then("a normal response should have been produced by the forward target")
|
||||
mockEndpoint.assertIsSatisfied
|
||||
then("the test message must have been sent to mock:mock")
|
||||
mockEndpoint.assertIsSatisfied()
|
||||
}
|
||||
|
||||
scenario("produce message, forward failure response to a producing target actor and produce response to direct:forward-test-1") {
|
||||
given("a registered one-way producer configured with a forward target")
|
||||
val target = actorOf(Props[ProducingForwardTarget]
|
||||
val producer = actorOf(Props(new TestForwarder("direct:producer-test-2", target))
|
||||
"produces message twoway without sender reference" in {
|
||||
given("a registered two-way producer")
|
||||
val producer = system.actorOf(Props(new TestProducer("direct:producer-test-1")))
|
||||
|
||||
when("a test message causing an exception is sent to the producer with !")
|
||||
mockEndpoint.expectedMessageCount(1)
|
||||
mockEndpoint.message(0).body().isInstanceOf(classOf[Failure])
|
||||
val result = producer.!(Message("fail"))(Some(producer))
|
||||
when("a test message is sent to the producer with !")
|
||||
mockEndpoint.expectedBodiesReceived("test")
|
||||
producer ! CamelMessage("test", Map())
|
||||
|
||||
then("a failure response should have been produced by the forward target")
|
||||
mockEndpoint.assertIsSatisfied
|
||||
then("there must be only a warning that there's no sender reference")
|
||||
mockEndpoint.assertIsSatisfied()
|
||||
}
|
||||
}
|
||||
|
||||
feature("Produce a message to an async Camel route and then forward the response") {
|
||||
"A Producer on an async Camel route" must {
|
||||
|
||||
scenario("produce message, forward normal response to a replying target actor and receive response") {
|
||||
given("a registered two-way producer configured with a forward target")
|
||||
val target = actorOf(Props[ReplyingForwardTarget]
|
||||
val producer = actorOf(Props(new TestForwarder("direct:producer-test-3", target))
|
||||
"produce message to direct:producer-test-3 and receive normal response" in {
|
||||
given("a registered two-way producer")
|
||||
val producer = system.actorOf(Props(new TestProducer("direct:producer-test-3")))
|
||||
|
||||
when("a test message is sent to the producer with ?")
|
||||
val message = Message("test", Map(Message.MessageExchangeId -> "123"))
|
||||
val result = (producer ? message).as[Message].get
|
||||
val message = CamelMessage("test", Map(CamelMessage.MessageExchangeId -> "123"))
|
||||
val future = producer.ask(message)(timeout)
|
||||
|
||||
then("a normal response should have been returned by the forward target")
|
||||
assert(result.headers(Message.MessageExchangeId) === "123")
|
||||
assert(result.headers("test") === "result")
|
||||
Await.result(future, timeout) match {
|
||||
case result: CamelMessage ⇒
|
||||
then("a normal response must have been returned by the producer")
|
||||
val expected = CamelMessage("received test", Map(CamelMessage.MessageExchangeId -> "123"))
|
||||
assert(result === expected)
|
||||
case unexpected ⇒ fail("Actor responded with unexpected message:" + unexpected)
|
||||
}
|
||||
}
|
||||
|
||||
scenario("produce message, forward failure response to a replying target actor and receive response") {
|
||||
given("a registered two-way producer configured with a forward target")
|
||||
val target = actorOf(Props[ReplyingForwardTarget]
|
||||
val producer = actorOf(Props(new TestForwarder("direct:producer-test-3", target))
|
||||
"produce message to direct:producer-test-3 and receive failure response" in {
|
||||
given("a registered two-way producer")
|
||||
val producer = system.actorOf(Props(new TestProducer("direct:producer-test-3")))
|
||||
|
||||
when("a test message causing an exception is sent to the producer with ?")
|
||||
val message = Message("fail", Map(Message.MessageExchangeId -> "123"))
|
||||
val result = (producer ? message).as[Failure]
|
||||
|
||||
then("a failure response should have been returned by the forward target")
|
||||
val expectedFailureText = result.get.cause.getMessage
|
||||
val expectedHeaders = result.get.headers
|
||||
val message = CamelMessage("fail", Map(CamelMessage.MessageExchangeId -> "123"))
|
||||
val future = producer.ask(message)(timeout)
|
||||
Await.result(future, timeout) match {
|
||||
case result: Failure ⇒
|
||||
then("a failure response must have been returned by the producer")
|
||||
val expectedFailureText = result.cause.getMessage
|
||||
val expectedHeaders = result.headers
|
||||
assert(expectedFailureText === "failure")
|
||||
assert(expectedHeaders === Map(Message.MessageExchangeId -> "123", "test" -> "failure"))
|
||||
assert(expectedHeaders === Map(CamelMessage.MessageExchangeId -> "123"))
|
||||
case unexpected ⇒ fail("Actor responded with unexpected message:" + unexpected)
|
||||
}
|
||||
}
|
||||
|
||||
scenario("produce message, forward normal response to a producing target actor and produce response to direct:forward-test-1") {
|
||||
"produce message, forward normal response of direct:producer-test-2 to a replying target actor and receive response" in {
|
||||
given("a registered two-way producer configured with a forward target")
|
||||
val target = system.actorOf(Props[ReplyingForwardTarget])
|
||||
val producer = system.actorOf(Props(new TestForwarder("direct:producer-test-2", target)))
|
||||
|
||||
when("a test message is sent to the producer with ?")
|
||||
val message = CamelMessage("test", Map(CamelMessage.MessageExchangeId -> "123"))
|
||||
val future = producer.ask(message)(timeout)
|
||||
|
||||
Await.result(future, timeout) match {
|
||||
case result: CamelMessage ⇒
|
||||
then("a normal response must have been returned by the forward target")
|
||||
val expected = CamelMessage("received test", Map(CamelMessage.MessageExchangeId -> "123", "test" -> "result"))
|
||||
assert(result === expected)
|
||||
case unexpected ⇒ fail("Actor responded with unexpected message:" + unexpected)
|
||||
}
|
||||
}
|
||||
|
||||
"produce message, forward failure response of direct:producer-test-2 to a replying target actor and receive response" in {
|
||||
given("a registered two-way producer configured with a forward target")
|
||||
val target = system.actorOf(Props[ReplyingForwardTarget])
|
||||
val producer = system.actorOf(Props(new TestForwarder("direct:producer-test-2", target)))
|
||||
|
||||
when("a test message causing an exception is sent to the producer with ?")
|
||||
val message = CamelMessage("fail", Map(CamelMessage.MessageExchangeId -> "123"))
|
||||
val future = producer.ask(message)(timeout)
|
||||
Await.result(future, timeout) match {
|
||||
case failure: Failure ⇒
|
||||
then("a failure response must have been returned by the forward target")
|
||||
val expectedFailureText = failure.cause.getMessage
|
||||
val expectedHeaders = failure.headers
|
||||
assert(expectedFailureText === "failure")
|
||||
assert(expectedHeaders === Map(CamelMessage.MessageExchangeId -> "123", "test" -> "failure"))
|
||||
case unexpected ⇒ fail("Actor responded with unexpected message:" + unexpected)
|
||||
}
|
||||
}
|
||||
|
||||
"produce message, forward normal response to a producing target actor and produce response to direct:forward-test-1" in {
|
||||
given("a registered one-way producer configured with a forward target")
|
||||
val target = actorOf(Props[ProducingForwardTarget]
|
||||
val producer = actorOf(Props(new TestForwarder("direct:producer-test-3", target))
|
||||
val target = system.actorOf(Props[ProducingForwardTarget])
|
||||
val producer = system.actorOf(Props(new TestForwarder("direct:producer-test-2", target)))
|
||||
|
||||
when("a test message is sent to the producer with !")
|
||||
mockEndpoint.expectedBodiesReceived("received test")
|
||||
val result = producer.!(Message("test"))(Some(producer))
|
||||
producer.tell(CamelMessage("test", Map()), producer)
|
||||
|
||||
then("a normal response should have been produced by the forward target")
|
||||
mockEndpoint.assertIsSatisfied
|
||||
then("a normal response must have been produced by the forward target")
|
||||
mockEndpoint.assertIsSatisfied()
|
||||
}
|
||||
|
||||
scenario("produce message, forward failure response to a producing target actor and produce response to direct:forward-test-1") {
|
||||
"produce message, forward failure response to a producing target actor and produce response to direct:forward-test-1" in {
|
||||
given("a registered one-way producer configured with a forward target")
|
||||
val target = actorOf(Props[ProducingForwardTarget]
|
||||
val producer = actorOf(Props(new TestForwarder("direct:producer-test-3", target))
|
||||
|
||||
val target = system.actorOf(Props[ProducingForwardTarget])
|
||||
val producer = system.actorOf(Props(new TestForwarder("direct:producer-test-2", target)))
|
||||
|
||||
when("a test message causing an exception is sent to the producer with !")
|
||||
mockEndpoint.expectedMessageCount(1)
|
||||
mockEndpoint.message(0).body().isInstanceOf(classOf[Failure])
|
||||
val result = producer.!(Message("fail"))(Some(producer))
|
||||
producer.tell(CamelMessage("fail", Map()), producer)
|
||||
|
||||
then("a failure response should have been produced by the forward target")
|
||||
mockEndpoint.assertIsSatisfied
|
||||
then("a failure response must have been produced by the forward target")
|
||||
mockEndpoint.assertIsSatisfied()
|
||||
}
|
||||
|
||||
"produce message, forward normal response from direct:producer-test-3 to a replying target actor and receive response" in {
|
||||
given("a registered two-way producer configured with a forward target")
|
||||
val target = system.actorOf(Props[ReplyingForwardTarget])
|
||||
val producer = system.actorOf(Props(new TestForwarder("direct:producer-test-3", target)))
|
||||
|
||||
when("a test message is sent to the producer with ?")
|
||||
val message = CamelMessage("test", Map(CamelMessage.MessageExchangeId -> "123"))
|
||||
|
||||
val future = producer.ask(message)(timeout)
|
||||
|
||||
then("a normal response must have been returned by the forward target")
|
||||
Await.result(future, timeout) match {
|
||||
case message: CamelMessage ⇒
|
||||
val expected = CamelMessage("received test", Map(CamelMessage.MessageExchangeId -> "123", "test" -> "result"))
|
||||
assert(message === expected)
|
||||
case unexpected ⇒ fail("Actor responded with unexpected message:" + unexpected)
|
||||
}
|
||||
}
|
||||
|
||||
private def mockEndpoint = CamelContextManager.mandatoryContext.getEndpoint("mock:mock", classOf[MockEndpoint])
|
||||
"produce message, forward failure response from direct:producer-test-3 to a replying target actor and receive response" in {
|
||||
given("a registered two-way producer configured with a forward target")
|
||||
val target = system.actorOf(Props[ReplyingForwardTarget])
|
||||
val producer = system.actorOf(Props(new TestForwarder("direct:producer-test-3", target)))
|
||||
|
||||
when("a test message causing an exception is sent to the producer with !!")
|
||||
val message = CamelMessage("fail", Map(CamelMessage.MessageExchangeId -> "123"))
|
||||
val future = producer.ask(message)(timeout)
|
||||
Await.result(future, timeout) match {
|
||||
case failure: Failure ⇒
|
||||
then("a failure response must have been returned by the forward target")
|
||||
val expectedFailureText = failure.cause.getMessage
|
||||
val expectedHeaders = failure.headers
|
||||
assert(expectedFailureText === "failure")
|
||||
assert(expectedHeaders === Map(CamelMessage.MessageExchangeId -> "123", "test" -> "failure"))
|
||||
case unexpected ⇒ fail("Actor responded with unexpected message:" + unexpected)
|
||||
}
|
||||
}
|
||||
|
||||
"produce message, forward normal response from direct:producer-test-3 to a producing target actor and produce response to direct:forward-test-1" in {
|
||||
given("a registered one-way producer configured with a forward target")
|
||||
val target = system.actorOf(Props[ProducingForwardTarget])
|
||||
val producer = system.actorOf(Props(new TestForwarder("direct:producer-test-3", target)))
|
||||
|
||||
when("a test message is sent to the producer with !")
|
||||
mockEndpoint.expectedBodiesReceived("received test")
|
||||
producer.tell(CamelMessage("test", Map()), producer)
|
||||
|
||||
then("a normal response must have been produced by the forward target")
|
||||
mockEndpoint.assertIsSatisfied()
|
||||
}
|
||||
|
||||
"produce message, forward failure response from direct:producer-test-3 to a producing target actor and produce response to direct:forward-test-1" in {
|
||||
given("a registered one-way producer configured with a forward target")
|
||||
val target = system.actorOf(Props[ProducingForwardTarget])
|
||||
val producer = system.actorOf(Props(new TestForwarder("direct:producer-test-3", target)))
|
||||
|
||||
when("a test message causing an exception is sent to the producer with !")
|
||||
mockEndpoint.expectedMessageCount(1)
|
||||
mockEndpoint.message(0).body().isInstanceOf(classOf[Failure])
|
||||
producer.tell(CamelMessage("fail", Map()), producer)
|
||||
|
||||
then("a failure response must have been produced by the forward target")
|
||||
mockEndpoint.assertIsSatisfied()
|
||||
}
|
||||
}
|
||||
|
||||
private def mockEndpoint = camel.context.getEndpoint("mock:mock", classOf[MockEndpoint])
|
||||
}
|
||||
|
||||
object ProducerFeatureTest {
|
||||
|
||||
class TestProducer(uri: String, upper: Boolean = false) extends Actor with Producer {
|
||||
def endpointUri = uri
|
||||
|
||||
override protected def receiveBeforeProduce = {
|
||||
case msg: Message ⇒ if (upper) msg.transformBody { body: String ⇒ body.toUpperCase } else msg
|
||||
case msg: CamelMessage ⇒ if (upper) msg.mapBody {
|
||||
body: String ⇒ body.toUpperCase
|
||||
}
|
||||
else msg
|
||||
}
|
||||
}
|
||||
|
||||
class TestForwarder(uri: String, target: ActorRef) extends Actor with Producer {
|
||||
def endpointUri = uri
|
||||
|
||||
override protected def receiveAfterProduce = {
|
||||
case msg ⇒ target forward msg
|
||||
}
|
||||
|
|
@ -252,17 +284,22 @@ object ProducerFeatureTest {
|
|||
|
||||
class TestResponder extends Actor {
|
||||
protected def receive = {
|
||||
case msg: Message ⇒ msg.body match {
|
||||
case "fail" ⇒ sender ! Failure(new Exception("failure"), msg.headers)
|
||||
case _ ⇒ sender ! (msg.transformBody { body: String ⇒ "received %s" format body })
|
||||
case msg: CamelMessage ⇒ msg.body match {
|
||||
case "fail" ⇒ context.sender ! (Failure(new Exception("failure"), msg.headers))
|
||||
case _ ⇒
|
||||
context.sender ! (msg.mapBody {
|
||||
body: String ⇒ "received %s" format body
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ReplyingForwardTarget extends Actor {
|
||||
protected def receive = {
|
||||
case msg: Message ⇒ sender ! msg.addHeader("test" -> "result")
|
||||
case msg: Failure ⇒ sender ! Failure(msg.cause, msg.headers + ("test" -> "failure"))
|
||||
case msg: CamelMessage ⇒
|
||||
context.sender ! (msg.addHeader("test" -> "result"))
|
||||
case msg: Failure ⇒
|
||||
context.sender ! (Failure(msg.cause, msg.headers + ("test" -> "failure")))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -270,14 +307,15 @@ object ProducerFeatureTest {
|
|||
def endpointUri = "direct:forward-test-1"
|
||||
}
|
||||
|
||||
class TestRoute extends RouteBuilder {
|
||||
val responder = actorOf(Props[TestResponder]
|
||||
class TestRoute(system: ActorSystem) extends RouteBuilder {
|
||||
val responder = system.actorOf(Props[TestResponder], name = "TestResponder")
|
||||
|
||||
def configure {
|
||||
from("direct:forward-test-1").to("mock:mock")
|
||||
// for one-way messaging tests
|
||||
from("direct:producer-test-1").to("mock:mock")
|
||||
// for two-way messaging tests (async)
|
||||
from("direct:producer-test-3").to("actor:uuid:%s" format responder.uuid)
|
||||
from("direct:producer-test-3").to(responder)
|
||||
// for two-way messaging tests (sync)
|
||||
from("direct:producer-test-2").process(new Processor() {
|
||||
def process(exchange: Exchange) = {
|
||||
|
|
@ -289,4 +327,5 @@ object ProducerFeatureTest {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camel
|
||||
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
import org.scalatest.WordSpec
|
||||
import akka.camel.TestSupport.SharedCamelSystem
|
||||
import akka.util.duration._
|
||||
import akka.actor.{ ActorRef, Props }
|
||||
|
||||
class ProducerRegistryTest extends WordSpec with MustMatchers with SharedCamelSystem {
|
||||
|
||||
"A ProducerRegistry" must {
|
||||
def newEmptyActor: ActorRef = system.actorOf(Props.empty)
|
||||
def registerProcessorFor(actorRef: ActorRef) = camel.registerProducer(actorRef, "mock:mock")._2
|
||||
|
||||
"register a started SendProcessor for the producer, which is stopped when the actor is stopped" in {
|
||||
val actorRef = newEmptyActor
|
||||
val processor = registerProcessorFor(actorRef)
|
||||
camel.awaitActivation(actorRef, 1 second)
|
||||
processor.isStarted must be(true)
|
||||
system.stop(actorRef)
|
||||
camel.awaitDeactivation(actorRef, 1 second)
|
||||
(processor.isStopping || processor.isStopped) must be(true)
|
||||
}
|
||||
"remove and stop the SendProcessor if the actorRef is registered" in {
|
||||
val actorRef = newEmptyActor
|
||||
val processor = registerProcessorFor(actorRef)
|
||||
camel.unregisterProducer(actorRef)
|
||||
(processor.isStopping || processor.isStopped) must be(true)
|
||||
}
|
||||
"remove and stop only the SendProcessor for the actorRef that is registered" in {
|
||||
val actorRef1 = newEmptyActor
|
||||
val actorRef2 = newEmptyActor
|
||||
val processor = registerProcessorFor(actorRef2)
|
||||
// this generates a warning on the activationTracker.
|
||||
camel.unregisterProducer(actorRef1)
|
||||
(!processor.isStopped && !processor.isStopping) must be(true)
|
||||
|
||||
camel.unregisterProducer(actorRef2)
|
||||
(processor.isStopping || processor.isStopped) must be(true)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
90
akka-camel/src/test/scala/akka/camel/TestSupport.scala
Normal file
90
akka-camel/src/test/scala/akka/camel/TestSupport.scala
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camel
|
||||
|
||||
import akka.actor.{ Props, ActorSystem, Actor }
|
||||
import akka.util.duration._
|
||||
import java.util.concurrent.{ TimeoutException, ExecutionException, TimeUnit }
|
||||
import org.scalatest.{ BeforeAndAfterEach, BeforeAndAfterAll, Suite }
|
||||
import org.scalatest.matchers.{ BePropertyMatcher, BePropertyMatchResult }
|
||||
import akka.util.{ FiniteDuration, Duration }
|
||||
|
||||
private[camel] object TestSupport {
|
||||
|
||||
def start(actor: ⇒ Actor)(implicit system: ActorSystem) = {
|
||||
val actorRef = system.actorOf(Props(actor))
|
||||
CamelExtension(system).awaitActivation(actorRef, 1 second)
|
||||
actorRef
|
||||
}
|
||||
|
||||
private[camel] implicit def camelToTestWrapper(camel: Camel) = new CamelTestWrapper(camel)
|
||||
|
||||
class CamelTestWrapper(camel: Camel) {
|
||||
/**
|
||||
* Sends msg to the endpoint and returns response.
|
||||
* It only waits for the response until timeout passes.
|
||||
* This is to reduce cases when unit-tests block infinitely.
|
||||
*/
|
||||
def sendTo(to: String, msg: String, timeout: Duration = 1 second): AnyRef = {
|
||||
try {
|
||||
camel.template.asyncRequestBody(to, msg).get(timeout.toNanos, TimeUnit.NANOSECONDS)
|
||||
} catch {
|
||||
case e: ExecutionException ⇒ throw e.getCause
|
||||
case e: TimeoutException ⇒ throw new AssertionError("Failed to get response to message [%s], send to endpoint [%s], within [%s]" format (msg, to, timeout), e)
|
||||
}
|
||||
}
|
||||
|
||||
def routeCount = camel.context.getRoutes().size()
|
||||
}
|
||||
|
||||
@deprecated
|
||||
trait MessageSugar {
|
||||
def Message(body: Any) = akka.camel.CamelMessage(body, Map.empty)
|
||||
def Message(body: Any, headers: Map[String, Any]) = akka.camel.CamelMessage(body, headers)
|
||||
}
|
||||
|
||||
trait SharedCamelSystem extends BeforeAndAfterAll { this: Suite ⇒
|
||||
implicit lazy val system = ActorSystem("test")
|
||||
implicit lazy val camel = CamelExtension(system)
|
||||
|
||||
abstract override protected def afterAll() {
|
||||
super.afterAll()
|
||||
system.shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
trait NonSharedCamelSystem extends BeforeAndAfterEach { this: Suite ⇒
|
||||
implicit var system: ActorSystem = _
|
||||
implicit var camel: Camel = _
|
||||
|
||||
override protected def beforeEach() {
|
||||
super.beforeEach()
|
||||
system = ActorSystem("test")
|
||||
camel = CamelExtension(system)
|
||||
}
|
||||
|
||||
override protected def afterEach() {
|
||||
system.shutdown()
|
||||
super.afterEach()
|
||||
}
|
||||
|
||||
}
|
||||
def time[A](block: ⇒ A): FiniteDuration = {
|
||||
val start = System.currentTimeMillis()
|
||||
block
|
||||
val duration = System.currentTimeMillis() - start
|
||||
duration millis
|
||||
}
|
||||
|
||||
def anInstanceOf[T](implicit manifest: Manifest[T]) = {
|
||||
val clazz = manifest.erasure.asInstanceOf[Class[T]]
|
||||
new BePropertyMatcher[AnyRef] {
|
||||
def apply(left: AnyRef) = BePropertyMatchResult(
|
||||
clazz.isAssignableFrom(left.getClass),
|
||||
"an instance of " + clazz.getName)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
101
akka-camel/src/test/scala/akka/camel/UntypedProducerTest.scala
Normal file
101
akka-camel/src/test/scala/akka/camel/UntypedProducerTest.scala
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camel
|
||||
|
||||
import org.apache.camel.{ Exchange, Processor }
|
||||
import org.apache.camel.builder.RouteBuilder
|
||||
import org.apache.camel.component.mock.MockEndpoint
|
||||
|
||||
import akka.camel.TestSupport.SharedCamelSystem
|
||||
import akka.actor.Props
|
||||
import akka.pattern._
|
||||
import akka.dispatch.Await
|
||||
import akka.util.duration._
|
||||
import org.scalatest._
|
||||
|
||||
class UntypedProducerTest extends WordSpec with BeforeAndAfterAll with BeforeAndAfterEach with SharedCamelSystem with GivenWhenThen {
|
||||
import UntypedProducerTest._
|
||||
val timeout = 1 second
|
||||
override protected def beforeAll = {
|
||||
camel.context.addRoutes(new TestRoute)
|
||||
}
|
||||
|
||||
override protected def afterEach = {
|
||||
mockEndpoint.reset
|
||||
}
|
||||
|
||||
"An UntypedProducer producing a message to a sync Camel route" must {
|
||||
|
||||
"produce a message and receive a normal response" in {
|
||||
given("a registered two-way producer")
|
||||
val producer = system.actorOf(Props[SampleUntypedReplyingProducer])
|
||||
|
||||
when("a test message is sent to the producer with !!")
|
||||
val message = CamelMessage("test", Map(CamelMessage.MessageExchangeId -> "123"))
|
||||
val future = producer.ask(message)(timeout)
|
||||
then("a normal response should have been returned by the producer")
|
||||
val expected = CamelMessage("received test", Map(CamelMessage.MessageExchangeId -> "123"))
|
||||
Await.result(future, timeout) match {
|
||||
case result: CamelMessage ⇒ assert(result === expected)
|
||||
case unexpected ⇒ fail("Actor responded with unexpected message:" + unexpected)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
"produce a message and receive a failure response" in {
|
||||
given("a registered two-way producer")
|
||||
val producer = system.actorOf(Props[SampleUntypedReplyingProducer])
|
||||
|
||||
when("a test message causing an exception is sent to the producer with !!")
|
||||
val message = CamelMessage("fail", Map(CamelMessage.MessageExchangeId -> "123"))
|
||||
val future = producer.ask(message)(timeout)
|
||||
then("a failure response should have been returned by the producer")
|
||||
Await.result(future, timeout) match {
|
||||
case result: Failure ⇒ {
|
||||
val expectedFailureText = result.cause.getMessage
|
||||
val expectedHeaders = result.headers
|
||||
assert(expectedFailureText === "failure")
|
||||
assert(expectedHeaders === Map(CamelMessage.MessageExchangeId -> "123"))
|
||||
}
|
||||
case unexpected ⇒ fail("Actor responded with unexpected message:" + unexpected)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
"An UntypedProducer producing a message to a sync Camel route and then forwarding the response" must {
|
||||
|
||||
"produce a message and send a normal response to direct:forward-test-1" in {
|
||||
given("a registered one-way producer configured with a forward target")
|
||||
val producer = system.actorOf(Props[SampleUntypedForwardingProducer])
|
||||
|
||||
when("a test message is sent to the producer with !")
|
||||
mockEndpoint.expectedBodiesReceived("received test")
|
||||
val result = producer.tell(CamelMessage("test", Map[String, Any]()), producer)
|
||||
|
||||
then("a normal response should have been sent")
|
||||
mockEndpoint.assertIsSatisfied
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private def mockEndpoint = camel.context.getEndpoint("mock:mock", classOf[MockEndpoint])
|
||||
}
|
||||
|
||||
object UntypedProducerTest {
|
||||
class TestRoute extends RouteBuilder {
|
||||
def configure {
|
||||
from("direct:forward-test-1").to("mock:mock")
|
||||
from("direct:producer-test-1").process(new Processor() {
|
||||
def process(exchange: Exchange) = {
|
||||
exchange.getIn.getBody match {
|
||||
case "fail" ⇒ throw new Exception("failure")
|
||||
case body ⇒ exchange.getOut.setBody("received %s" format body)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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]
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)) }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camel.internal.component
|
||||
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
import akka.util.duration._
|
||||
import akka.camel.TestSupport.SharedCamelSystem
|
||||
import org.apache.camel.Component
|
||||
import org.scalatest.WordSpec
|
||||
|
||||
class ActorComponentConfigurationTest extends WordSpec with MustMatchers with SharedCamelSystem {
|
||||
|
||||
val component: Component = camel.context.getComponent("actor")
|
||||
|
||||
"Endpoint url config must be correctly parsed" in {
|
||||
val actorEndpointConfig = component.createEndpoint("actor://path:akka://test/user/$a?autoack=false&replyTimeout=987000000+nanos").asInstanceOf[ActorEndpointConfig]
|
||||
|
||||
actorEndpointConfig must have(
|
||||
'endpointUri("actor://path:akka://test/user/$a?autoack=false&replyTimeout=987000000+nanos"),
|
||||
'path(ActorEndpointPath.fromCamelPath("path:akka://test/user/$a")),
|
||||
'autoack(false),
|
||||
'replyTimeout(987000000 nanos))
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
/**
|
||||
* Copyright (C) 2009when2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camel.internal.component
|
||||
|
||||
import org.scalatest.mock.MockitoSugar
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
import akka.camel.TestSupport.SharedCamelSystem
|
||||
import akka.actor.Props
|
||||
import org.scalatest.WordSpec
|
||||
|
||||
class ActorEndpointPathTest extends WordSpec with SharedCamelSystem with MustMatchers with MockitoSugar {
|
||||
|
||||
def find(path: String) = ActorEndpointPath.fromCamelPath("path:" + path).findActorIn(system)
|
||||
|
||||
"findActorIn returns Some(actor ref) if actor exists" in {
|
||||
val path = system.actorOf(Props(behavior = ctx ⇒ { case _ ⇒ {} })).path
|
||||
find(path.toString) must be('defined)
|
||||
}
|
||||
|
||||
"findActorIn returns None" when {
|
||||
"invalid path" in { find("some_invalid_path") must be(None) }
|
||||
"non existing valid path" in { find("akka://system/user/$a") must be(None) }
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,340 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camel.internal.component
|
||||
|
||||
import org.scalatest.mock.MockitoSugar
|
||||
import org.mockito.Matchers.{ eq ⇒ the, any }
|
||||
import org.mockito.Mockito._
|
||||
import org.apache.camel.AsyncCallback
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import akka.util.duration._
|
||||
import akka.util.Duration
|
||||
import akka.testkit.{ TestKit, TestProbe }
|
||||
import java.lang.String
|
||||
import akka.actor.{ ActorRef, Props, ActorSystem, Actor }
|
||||
import akka.camel._
|
||||
import internal.CamelExchangeAdapter
|
||||
import org.scalatest.{ Suite, WordSpec, BeforeAndAfterAll, BeforeAndAfterEach }
|
||||
import akka.camel.TestSupport._
|
||||
import java.util.concurrent.{ TimeoutException, CountDownLatch, TimeUnit }
|
||||
import org.mockito.{ ArgumentMatcher, Matchers, Mockito }
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
|
||||
class ActorProducerTest extends TestKit(ActorSystem("test")) with WordSpec with MustMatchers with ActorProducerFixture {
|
||||
|
||||
"ActorProducer" when {
|
||||
|
||||
"synchronous" when {
|
||||
|
||||
"consumer actor doesnt exist" must {
|
||||
"set failure message on exchange" in {
|
||||
producer = given(actor = null)
|
||||
producer.processExchangeAdapter(exchange)
|
||||
|
||||
verify(exchange).setFailure(any[Failure])
|
||||
}
|
||||
}
|
||||
|
||||
"in-only" must {
|
||||
def producer = given(outCapable = false)
|
||||
|
||||
"pass the message to the consumer" in {
|
||||
producer.processExchangeAdapter(exchange)
|
||||
within(1 second)(probe.expectMsg(message))
|
||||
}
|
||||
|
||||
"not expect response and not block" in {
|
||||
time(producer.processExchangeAdapter(exchange)) must be < (30 millis)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
"out capable" when {
|
||||
"response is sent back by actor" must {
|
||||
|
||||
"get a response" in {
|
||||
producer = given(actor = echoActor, outCapable = true)
|
||||
|
||||
producer.processExchangeAdapter(exchange)
|
||||
|
||||
verify(exchange).setResponse(msg("received " + message))
|
||||
}
|
||||
}
|
||||
|
||||
"response is not sent by actor" must {
|
||||
|
||||
def process() = {
|
||||
producer = given(outCapable = true, replyTimeout = 100 millis)
|
||||
time(producer.processExchangeAdapter(exchange))
|
||||
}
|
||||
|
||||
"timeout after replyTimeout" in {
|
||||
val duration = process()
|
||||
duration must (be >= (100 millis) and be < (300 millis))
|
||||
}
|
||||
|
||||
"never set the response on exchange" in {
|
||||
process()
|
||||
verify(exchange, Mockito.never()).setResponse(any[CamelMessage])
|
||||
}
|
||||
|
||||
"set failure message to timeout" in {
|
||||
process()
|
||||
verify(exchange).setFailure(any[Failure])
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//TODO: write more tests for synchronous process(exchange) method
|
||||
|
||||
}
|
||||
|
||||
"asynchronous" when {
|
||||
|
||||
def verifyFailureIsSet {
|
||||
producer.processExchangeAdapter(exchange, asyncCallback)
|
||||
asyncCallback.awaitCalled()
|
||||
verify(exchange).setFailure(any[Failure])
|
||||
}
|
||||
|
||||
"out-capable" when {
|
||||
|
||||
"consumer actor doesnt exist" must {
|
||||
"set failure message on exchange" in {
|
||||
producer = given(actor = null, outCapable = true)
|
||||
verifyFailureIsSet
|
||||
}
|
||||
}
|
||||
|
||||
"response is ok" must {
|
||||
"get a response and async callback as soon as it gets the response (but not before)" in {
|
||||
producer = given(outCapable = true)
|
||||
|
||||
val doneSync = producer.processExchangeAdapter(exchange, asyncCallback)
|
||||
|
||||
asyncCallback.expectNoCallWithin(100 millis); info("no async callback before response")
|
||||
|
||||
within(1 second) {
|
||||
probe.expectMsgType[CamelMessage]
|
||||
probe.sender ! "some message"
|
||||
}
|
||||
doneSync must be(false); info("done async")
|
||||
|
||||
asyncCallback.expectDoneAsyncWithin(1 second); info("async callback received")
|
||||
verify(exchange).setResponse(msg("some message")); info("response as expected")
|
||||
}
|
||||
}
|
||||
|
||||
"response is Failure" must {
|
||||
"set an exception on exchange" in {
|
||||
val failure = Failure(new RuntimeException("some failure"))
|
||||
|
||||
producer = given(outCapable = true)
|
||||
|
||||
producer.processExchangeAdapter(exchange, asyncCallback)
|
||||
|
||||
within(1 second) {
|
||||
probe.expectMsgType[CamelMessage]
|
||||
probe.sender ! failure
|
||||
asyncCallback.awaitCalled(remaining)
|
||||
}
|
||||
|
||||
verify(exchange).setFailure(failure)
|
||||
}
|
||||
}
|
||||
|
||||
"no response is sent within timeout" must {
|
||||
"set TimeoutException on exchange" in {
|
||||
producer = given(outCapable = true, replyTimeout = 10 millis)
|
||||
producer.processExchangeAdapter(exchange, asyncCallback)
|
||||
asyncCallback.awaitCalled(100 millis)
|
||||
verify(exchange).setFailure(Matchers.argThat(new ArgumentMatcher[Failure] {
|
||||
def matches(failure: AnyRef) = { failure.asInstanceOf[Failure].getCause must be(anInstanceOf[TimeoutException]); true }
|
||||
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
"in-only" when {
|
||||
|
||||
"consumer actor doesnt exist" must {
|
||||
"set failure message on exchange" in {
|
||||
producer = given(actor = null, outCapable = false)
|
||||
verifyFailureIsSet
|
||||
}
|
||||
}
|
||||
|
||||
"autoAck" must {
|
||||
|
||||
"get sync callback as soon as it sends a message" in {
|
||||
|
||||
producer = given(outCapable = false, autoAck = true)
|
||||
val doneSync = producer.processExchangeAdapter(exchange, asyncCallback)
|
||||
|
||||
doneSync must be(true); info("done sync")
|
||||
asyncCallback.expectDoneSyncWithin(1 second); info("async callback called")
|
||||
verify(exchange, never()).setResponse(any[CamelMessage]); info("no response forwarded to exchange")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
"manualAck" when {
|
||||
|
||||
"response is Ack" must {
|
||||
"get async callback" in {
|
||||
producer = given(outCapable = false, autoAck = false)
|
||||
|
||||
val doneSync = producer.processExchangeAdapter(exchange, asyncCallback)
|
||||
|
||||
doneSync must be(false)
|
||||
within(1 second) {
|
||||
probe.expectMsgType[CamelMessage]; info("message sent to consumer")
|
||||
probe.sender ! Ack
|
||||
asyncCallback.expectDoneAsyncWithin(remaining); info("async callback called")
|
||||
}
|
||||
verify(exchange, never()).setResponse(any[CamelMessage]); info("no response forwarded to exchange")
|
||||
}
|
||||
}
|
||||
|
||||
"expecting Ack or Failure message and some other message is sent as a response" must {
|
||||
"fail" in {
|
||||
producer = given(outCapable = false, autoAck = false)
|
||||
|
||||
producer.processExchangeAdapter(exchange, asyncCallback)
|
||||
|
||||
within(1 second) {
|
||||
probe.expectMsgType[CamelMessage]; info("message sent to consumer")
|
||||
probe.sender ! "some neither Ack nor Failure response"
|
||||
asyncCallback.expectDoneAsyncWithin(remaining); info("async callback called")
|
||||
}
|
||||
verify(exchange, never()).setResponse(any[CamelMessage]); info("no response forwarded to exchange")
|
||||
verify(exchange).setFailure(any[Failure]); info("failure set")
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
"no Ack is sent within timeout" must {
|
||||
"set failure on exchange" in {
|
||||
producer = given(outCapable = false, replyTimeout = 10 millis, autoAck = false)
|
||||
|
||||
producer.processExchangeAdapter(exchange, asyncCallback)
|
||||
asyncCallback.awaitCalled(100 millis)
|
||||
verify(exchange).setFailure(any[Failure])
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
"response is Failure" must {
|
||||
"set an exception on exchange" in {
|
||||
producer = given(outCapable = false, autoAck = false)
|
||||
|
||||
val doneSync = producer.processExchangeAdapter(exchange, asyncCallback)
|
||||
|
||||
doneSync must be(false)
|
||||
within(1 second) {
|
||||
probe.expectMsgType[CamelMessage]; info("message sent to consumer")
|
||||
probe.sender ! Failure(new Exception)
|
||||
asyncCallback.awaitCalled(remaining);
|
||||
}
|
||||
verify(exchange, never()).setResponse(any[CamelMessage]); info("no response forwarded to exchange")
|
||||
verify(exchange).setFailure(any[Failure]); info("failure set")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trait ActorProducerFixture extends MockitoSugar with BeforeAndAfterAll with BeforeAndAfterEach { self: TestKit with MustMatchers with Suite ⇒
|
||||
var camel: Camel = _
|
||||
var exchange: CamelExchangeAdapter = _
|
||||
var callback: AsyncCallback = _
|
||||
|
||||
var producer: ActorProducer = _
|
||||
var message: CamelMessage = _
|
||||
var probe: TestProbe = _
|
||||
var asyncCallback: TestAsyncCallback = _
|
||||
var actorEndpointPath: ActorEndpointPath = _
|
||||
var actorComponent: ActorComponent = _
|
||||
|
||||
override protected def beforeEach() {
|
||||
asyncCallback = createAsyncCallback
|
||||
|
||||
probe = TestProbe()
|
||||
camel = mock[Camel]
|
||||
exchange = mock[CamelExchangeAdapter]
|
||||
callback = mock[AsyncCallback]
|
||||
actorEndpointPath = mock[ActorEndpointPath]
|
||||
actorComponent = mock[ActorComponent]
|
||||
producer = new ActorProducer(config(), camel)
|
||||
message = CamelMessage(null, null)
|
||||
}
|
||||
|
||||
override protected def afterAll() {
|
||||
system.shutdown()
|
||||
}
|
||||
|
||||
def msg(s: String) = CamelMessage(s, Map.empty)
|
||||
|
||||
def given(actor: ActorRef = probe.ref, outCapable: Boolean = true, autoAck: Boolean = true, replyTimeout: Duration = Int.MaxValue seconds) = {
|
||||
prepareMocks(actor, outCapable = outCapable)
|
||||
new ActorProducer(config(isAutoAck = autoAck, _replyTimeout = replyTimeout), camel)
|
||||
}
|
||||
|
||||
def createAsyncCallback = new TestAsyncCallback
|
||||
|
||||
class TestAsyncCallback extends AsyncCallback {
|
||||
def expectNoCallWithin(duration: Duration) {
|
||||
if (callbackReceived.await(duration.toNanos, TimeUnit.NANOSECONDS)) fail("NOT expected callback, but received one!")
|
||||
}
|
||||
|
||||
def awaitCalled(timeout: Duration = 1 second) { valueWithin(1 second) }
|
||||
|
||||
val callbackReceived = new CountDownLatch(1)
|
||||
val callbackValue = new AtomicBoolean()
|
||||
|
||||
def done(doneSync: Boolean) {
|
||||
callbackValue set doneSync
|
||||
callbackReceived.countDown()
|
||||
}
|
||||
|
||||
private[this] def valueWithin(implicit timeout: Duration) = {
|
||||
if (!callbackReceived.await(timeout.toNanos, TimeUnit.NANOSECONDS)) fail("Callback not received!")
|
||||
callbackValue.get
|
||||
}
|
||||
|
||||
def expectDoneSyncWithin(implicit timeout: Duration) {
|
||||
if (!valueWithin(timeout)) fail("Expected to be done Synchronously")
|
||||
}
|
||||
def expectDoneAsyncWithin(implicit timeout: Duration) {
|
||||
if (valueWithin(timeout)) fail("Expected to be done Asynchronously")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
def config(endpointUri: String = "test-uri", isAutoAck: Boolean = true, _replyTimeout: Duration = Int.MaxValue seconds) = {
|
||||
val endpoint = new ActorEndpoint(endpointUri, actorComponent, actorEndpointPath, camel)
|
||||
endpoint.autoack = isAutoAck
|
||||
endpoint.replyTimeout = _replyTimeout
|
||||
endpoint
|
||||
}
|
||||
|
||||
def prepareMocks(actor: ActorRef, message: CamelMessage = message, outCapable: Boolean) {
|
||||
when(actorEndpointPath.findActorIn(any[ActorSystem])) thenReturn Option(actor)
|
||||
when(exchange.toRequestMessage(any[Map[String, Any]])) thenReturn message
|
||||
when(exchange.isOutCapable) thenReturn outCapable
|
||||
}
|
||||
|
||||
def echoActor = system.actorOf(Props(new Actor {
|
||||
protected def receive = {
|
||||
case msg ⇒ sender ! "received " + msg
|
||||
}
|
||||
}))
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.camel.internal.component
|
||||
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
import akka.util.duration._
|
||||
import akka.util.Duration
|
||||
import org.scalatest.WordSpec
|
||||
|
||||
class DurationConverterTest extends WordSpec with MustMatchers {
|
||||
import DurationTypeConverter._
|
||||
|
||||
"DurationTypeConverter must convert '10 nanos'" in {
|
||||
convertTo(classOf[Duration], "10 nanos") must be(10 nanos)
|
||||
}
|
||||
|
||||
"DurationTypeConverter must do the roundtrip" in {
|
||||
convertTo(classOf[Duration], DurationTypeConverter.toString(10 seconds)) must be(10 seconds)
|
||||
}
|
||||
|
||||
"DurationTypeConverter must throw if invalid format" in {
|
||||
intercept[Exception] {
|
||||
convertTo(classOf[Duration], "abc nanos") must be(10 nanos)
|
||||
}
|
||||
}
|
||||
|
||||
"DurationTypeConverter must throw if doesn't end with nanos" in {
|
||||
intercept[Exception] {
|
||||
convertTo(classOf[Duration], "10233") must be(10 nanos)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
3
akka-camel/src/test/scala/application.conf
Normal file
3
akka-camel/src/test/scala/application.conf
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
akka{
|
||||
loglevel = "ERROR"
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -13,9 +13,11 @@ Akka is using ``Scalariform`` to format the source code as part of the build. So
|
|||
Process
|
||||
-------
|
||||
|
||||
* Make sure you have signed the Akka CLA, if not, ask for it on the Mailing List.
|
||||
* Pick a ticket, if there is no ticket for your work then create one first.
|
||||
* Start working in a feature branch. Name it something like ``wip-<descriptive name or ticket number>-<your username>``.
|
||||
* When you are done send a request for review to the Akka developer mailing list. If successfully review, merge into master.
|
||||
* Start working in a feature branch. Name it something like ``wip-<ticket number>-<descriptive name>-<your username>``.
|
||||
* When you are done, create a GitHub Pull-Request towards the targeted branch and email the Akka Mailing List that you want it reviewed
|
||||
* When there's consensus on the review, someone from the Akka Core Team will merge it.
|
||||
|
||||
Commit messages
|
||||
---------------
|
||||
|
|
|
|||
|
|
@ -109,6 +109,23 @@ you might want to know how to serialize and deserialize them properly, here's th
|
|||
.. includecode:: code/akka/docs/serialization/SerializationDocTestBase.java
|
||||
:include: imports,actorref-serializer
|
||||
|
||||
Deep serialization of Actors
|
||||
----------------------------
|
||||
|
||||
The current recommended approach to do deep serialization of internal actor state is to use Event Sourcing,
|
||||
for more reading on the topic, see these examples:
|
||||
|
||||
`Martin Krasser on EventSourcing Part1 <http://krasserm.blogspot.com/2011/11/building-event-sourced-web-application.html>`_
|
||||
|
||||
`Martin Krasser on EventSourcing Part2 <http://krasserm.blogspot.com/2012/01/building-event-sourced-web-application.html>`_
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
Built-in API support for persisting Actors will come in a later release, see the roadmap for more info:
|
||||
|
||||
`Akka 2.0 roadmap <https://docs.google.com/a/typesafe.com/document/d/18W9-fKs55wiFNjXL9q50PYOnR7-nnsImzJqHOPPbM4E>`_
|
||||
|
||||
A Word About Java Serialization
|
||||
===============================
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
-----
|
||||
|
|
|
|||
|
|
@ -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
|
||||
-----------------
|
||||
|
||||
|
|
|
|||
|
|
@ -107,6 +107,23 @@ you might want to know how to serialize and deserialize them properly, here's th
|
|||
.. includecode:: code/akka/docs/serialization/SerializationDocSpec.scala
|
||||
:include: imports,actorref-serializer
|
||||
|
||||
Deep serialization of Actors
|
||||
----------------------------
|
||||
|
||||
The current recommended approach to do deep serialization of internal actor state is to use Event Sourcing,
|
||||
for more reading on the topic, see these examples:
|
||||
|
||||
`Martin Krasser on EventSourcing Part1 <http://krasserm.blogspot.com/2011/11/building-event-sourced-web-application.html>`_
|
||||
|
||||
`Martin Krasser on EventSourcing Part2 <http://krasserm.blogspot.com/2012/01/building-event-sourced-web-application.html>`_
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
Built-in API support for persisting Actors will come in a later release, see the roadmap for more info:
|
||||
|
||||
`Akka 2.0 roadmap <https://docs.google.com/a/typesafe.com/document/d/18W9-fKs55wiFNjXL9q50PYOnR7-nnsImzJqHOPPbM4E>`_
|
||||
|
||||
A Word About Java Serialization
|
||||
===============================
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue