Removed obsolete sample modules and cleaned up build file.
Signed-off-by: Jonas Bonér <jonas@jonasboner.com>
This commit is contained in:
parent
0a1740cd6d
commit
3640c09464
37 changed files with 11 additions and 1871 deletions
|
|
@ -1,46 +0,0 @@
|
||||||
Ants
|
|
||||||
====
|
|
||||||
|
|
||||||
Ants is written by Peter Vlugter.
|
|
||||||
|
|
||||||
Ants is roughly based on the Clojure [ants simulation][ants.clj] by Rich Hickey, and ported to Scala using [Akka][akka] and [Spde][spde].
|
|
||||||
|
|
||||||
Requirements
|
|
||||||
------------
|
|
||||||
|
|
||||||
To build and run Ants you need [Simple Build Tool][sbt] (sbt).
|
|
||||||
|
|
||||||
Running
|
|
||||||
-------
|
|
||||||
|
|
||||||
First time, 'sbt update' to get dependencies, then to run Ants use 'sbt run'.
|
|
||||||
Here is an example. First type 'sbt' to start SBT interactively, the run 'update' and 'run':
|
|
||||||
> cd $AKKA_HOME
|
|
||||||
|
|
||||||
> % sbt
|
|
||||||
|
|
||||||
> > update
|
|
||||||
|
|
||||||
> > project akka-sample-ants
|
|
||||||
|
|
||||||
> > run
|
|
||||||
|
|
||||||
|
|
||||||
Notice
|
|
||||||
------
|
|
||||||
|
|
||||||
Ants is roughly based on the Clojure ants simulation by Rich Hickey.
|
|
||||||
|
|
||||||
Copyright (c) Rich Hickey. All rights reserved.
|
|
||||||
The use and distribution terms for this software are covered by the
|
|
||||||
Common Public License 1.0 ([http://opensource.org/licenses/cpl1.0.php][cpl])
|
|
||||||
which can be found in the file cpl.txt at the root of this distribution.
|
|
||||||
By using this software in any fashion, you are agreeing to be bound by
|
|
||||||
the terms of this license.
|
|
||||||
You must not remove this notice, or any other, from this software.
|
|
||||||
|
|
||||||
[ants.clj]:http://clojure.googlegroups.com/web/ants.clj
|
|
||||||
[akka]:http://akkasource.org
|
|
||||||
[spde]:http://technically.us/spde/
|
|
||||||
[sbt]: http://code.google.com/p/simple-build-tool/
|
|
||||||
[cpl]: http://opensource.org/licenses/cpl1.0.php
|
|
||||||
|
|
@ -1,219 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
|
||||||
*/
|
|
||||||
|
|
||||||
package sample.ants
|
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import scala.util.Random.{nextInt => randomInt}
|
|
||||||
import akka.actor.{Actor, ActorRef, Scheduler}
|
|
||||||
import akka.actor.Actor.actorOf
|
|
||||||
import akka.stm._
|
|
||||||
|
|
||||||
object Config {
|
|
||||||
val Dim = 80 // dimensions of square world
|
|
||||||
val AntsSqrt = 20 // number of ants = AntsSqrt^2
|
|
||||||
val FoodPlaces = 35 // number of places with food
|
|
||||||
val FoodRange = 100 // range of amount of food at a place
|
|
||||||
val PherScale = 10 // scale factor for pheromone drawing
|
|
||||||
val AntMillis = 100 // how often an ant behaves (milliseconds)
|
|
||||||
val EvapMillis = 1000 // how often pheromone evaporation occurs (milliseconds)
|
|
||||||
val EvapRate = 0.99f // pheromone evaporation rate
|
|
||||||
val StartDelay = 1000 // delay before everything kicks off (milliseconds)
|
|
||||||
}
|
|
||||||
|
|
||||||
case class Ant(dir: Int, food: Boolean = false) {
|
|
||||||
def turn(i: Int) = copy(dir = Util.dirBound(dir + i))
|
|
||||||
def turnAround = turn(4)
|
|
||||||
def pickUp = copy(food = true)
|
|
||||||
def dropOff = copy(food = false)
|
|
||||||
}
|
|
||||||
|
|
||||||
case class Cell(food: Int = 0, pher: Float = 0, ant: Option[Ant] = None, home: Boolean = false) {
|
|
||||||
def addFood(i: Int) = copy(food = food + i)
|
|
||||||
def addPher(x: Float) = copy(pher = pher + x)
|
|
||||||
def alterPher(f: Float => Float) = copy(pher = f(pher))
|
|
||||||
def putAnt(antOpt: Option[Ant]) = copy(ant = antOpt)
|
|
||||||
def makeHome = copy(home = true)
|
|
||||||
}
|
|
||||||
|
|
||||||
object EmptyCell extends Cell
|
|
||||||
|
|
||||||
class Place(initCell: Cell = EmptyCell) extends Ref(initCell) {
|
|
||||||
def cell: Cell = getOrElse(EmptyCell)
|
|
||||||
def food: Int = cell.food
|
|
||||||
def food(i: Int) = alter(_.addFood(i))
|
|
||||||
def hasFood = food > 0
|
|
||||||
def pher: Float = cell.pher
|
|
||||||
def pher(f: Float => Float) = alter(_.alterPher(f))
|
|
||||||
def trail = alter(_.addPher(1))
|
|
||||||
def ant: Option[Ant] = cell.ant
|
|
||||||
def ant(f: Ant => Ant): Cell = alter(_.putAnt(ant map f))
|
|
||||||
def enter(antOpt: Option[Ant]): Cell = alter(_.putAnt(antOpt))
|
|
||||||
def enter(ant: Ant): Cell = enter(Some(ant))
|
|
||||||
def leave = enter(None)
|
|
||||||
def occupied: Boolean = ant.isDefined
|
|
||||||
def makeHome = alter(_.makeHome)
|
|
||||||
def home: Boolean = cell.home
|
|
||||||
}
|
|
||||||
|
|
||||||
case object Ping
|
|
||||||
|
|
||||||
object World {
|
|
||||||
import Config._
|
|
||||||
|
|
||||||
val homeOff = Dim / 4
|
|
||||||
lazy val places = Vector.fill(Dim, Dim)(new Place)
|
|
||||||
lazy val ants = setup
|
|
||||||
lazy val evaporator = actorOf[Evaporator]
|
|
||||||
|
|
||||||
private val snapshotFactory = TransactionFactory(readonly = true, familyName = "snapshot")
|
|
||||||
|
|
||||||
def snapshot = atomic(snapshotFactory) { Array.tabulate(Dim, Dim)(place(_, _).opt) }
|
|
||||||
|
|
||||||
def place(loc: (Int, Int)) = places(loc._1)(loc._2)
|
|
||||||
|
|
||||||
private def setup = atomic {
|
|
||||||
for (i <- 1 to FoodPlaces) {
|
|
||||||
place(randomInt(Dim), randomInt(Dim)) food (randomInt(FoodRange))
|
|
||||||
}
|
|
||||||
val homeRange = homeOff until (AntsSqrt + homeOff)
|
|
||||||
for (x <- homeRange; y <- homeRange) yield {
|
|
||||||
place(x, y).makeHome
|
|
||||||
place(x, y) enter Ant(randomInt(8))
|
|
||||||
actorOf(new AntActor(x, y))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def start = {
|
|
||||||
ants foreach pingEvery(AntMillis)
|
|
||||||
pingEvery(EvapMillis)(evaporator)
|
|
||||||
}
|
|
||||||
|
|
||||||
private def pingEvery(millis: Long)(actor: ActorRef) =
|
|
||||||
Scheduler.schedule(actor, Ping, Config.StartDelay, millis, TimeUnit.MILLISECONDS)
|
|
||||||
}
|
|
||||||
|
|
||||||
object Util {
|
|
||||||
import Config._
|
|
||||||
|
|
||||||
def bound(b: Int, n: Int) = {
|
|
||||||
val x = n % b
|
|
||||||
if (x < 0) x + b else x
|
|
||||||
}
|
|
||||||
|
|
||||||
def dirBound(n: Int) = bound(8, n)
|
|
||||||
def dimBound(n: Int) = bound(Dim, n)
|
|
||||||
|
|
||||||
val dirDelta = Map(0 -> (0, -1), 1 -> (1, -1), 2 -> (1, 0), 3 -> (1, 1),
|
|
||||||
4 -> (0, 1), 5 -> (-1, 1), 6 -> (-1, 0), 7 -> (-1, -1))
|
|
||||||
def deltaLoc(x: Int, y: Int, dir: Int) = {
|
|
||||||
val (dx, dy) = dirDelta(dirBound(dir))
|
|
||||||
(dimBound(x + dx), dimBound(y + dy))
|
|
||||||
}
|
|
||||||
|
|
||||||
def rankBy[A, B: Ordering](xs: Seq[A], f: A => B) = Map(xs.sortBy(f).zip(Stream from 1): _*)
|
|
||||||
|
|
||||||
def roulette(slices: Seq[Int]) = {
|
|
||||||
val total = slices.sum
|
|
||||||
val r = randomInt(total)
|
|
||||||
var i, sum = 0
|
|
||||||
while ((sum + slices(i)) <= r) {
|
|
||||||
sum += slices(i)
|
|
||||||
i += 1
|
|
||||||
}
|
|
||||||
i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
trait WorldActor extends Actor {
|
|
||||||
def act
|
|
||||||
def receive = { case Ping => act }
|
|
||||||
}
|
|
||||||
|
|
||||||
class AntActor(initLoc: (Int, Int)) extends WorldActor {
|
|
||||||
import World._
|
|
||||||
import Util._
|
|
||||||
|
|
||||||
val locRef = Ref(initLoc)
|
|
||||||
|
|
||||||
val name = "ant-from-" + initLoc._1 + "-" + initLoc._2
|
|
||||||
implicit val txFactory = TransactionFactory(familyName = name)
|
|
||||||
|
|
||||||
val homing = (p: Place) => p.pher + (100 * (if (p.home) 0 else 1))
|
|
||||||
val foraging = (p: Place) => p.pher + p.food
|
|
||||||
|
|
||||||
def loc = locRef.getOrElse(initLoc)
|
|
||||||
def newLoc(l: (Int, Int)) = locRef swap l
|
|
||||||
|
|
||||||
def act = atomic {
|
|
||||||
val (x, y) = loc
|
|
||||||
val current = place(x, y)
|
|
||||||
for (ant <- current.ant) {
|
|
||||||
val ahead = place(deltaLoc(x, y, ant.dir))
|
|
||||||
if (ant.food) { // homing
|
|
||||||
if (current.home) dropFood
|
|
||||||
else if (ahead.home && !ahead.occupied) move
|
|
||||||
else random(homing)
|
|
||||||
} else { // foraging
|
|
||||||
if (!current.home && current.hasFood) pickUpFood
|
|
||||||
else if (!ahead.home && ahead.hasFood && !ahead.occupied) move
|
|
||||||
else random(foraging)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def move = {
|
|
||||||
val (x, y) = loc
|
|
||||||
val from = place(x, y)
|
|
||||||
for (ant <- from.ant) {
|
|
||||||
val toLoc = deltaLoc(x, y, ant.dir)
|
|
||||||
val to = place(toLoc)
|
|
||||||
to enter ant
|
|
||||||
from.leave
|
|
||||||
if (!from.home) from.trail
|
|
||||||
newLoc(toLoc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def pickUpFood = {
|
|
||||||
val current = place(loc)
|
|
||||||
current food -1
|
|
||||||
current ant (_.pickUp.turnAround)
|
|
||||||
}
|
|
||||||
|
|
||||||
def dropFood = {
|
|
||||||
val current = place(loc)
|
|
||||||
current food +1
|
|
||||||
current ant (_.dropOff.turnAround)
|
|
||||||
}
|
|
||||||
|
|
||||||
def random[A: Ordering](ranking: Place => A) = {
|
|
||||||
val (x, y) = loc
|
|
||||||
val current = place(x, y)
|
|
||||||
for (ant <- current.ant) {
|
|
||||||
val delta = (turn: Int) => place(deltaLoc(x, y, ant.dir + turn))
|
|
||||||
val ahead = delta(0)
|
|
||||||
val aheadLeft = delta(-1)
|
|
||||||
val aheadRight = delta(+1)
|
|
||||||
val locations = Seq(ahead, aheadLeft, aheadRight)
|
|
||||||
val ranks = rankBy(locations, ranking)
|
|
||||||
val ranked = Seq(ranks(aheadLeft), (if (ahead.occupied) 0 else ranks(ahead)), ranks(aheadRight))
|
|
||||||
val dir = roulette(ranked) - 1
|
|
||||||
if (dir == 0) move
|
|
||||||
else current ant (_.turn(dir))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Evaporator extends WorldActor {
|
|
||||||
import Config._
|
|
||||||
import World._
|
|
||||||
|
|
||||||
implicit val txFactory = TransactionFactory(familyName = "evaporator")
|
|
||||||
val evaporate = (pher: Float) => pher * EvapRate
|
|
||||||
|
|
||||||
def act = for (x <- 0 until Dim; y <- 0 until Dim) {
|
|
||||||
atomic { place(x, y) pher evaporate }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
import sample.ants._
|
|
||||||
import sample.ants.Config._
|
|
||||||
|
|
||||||
val scale = 5
|
|
||||||
|
|
||||||
size(Dim * scale, Dim * scale)
|
|
||||||
smooth()
|
|
||||||
|
|
||||||
override def setup() {
|
|
||||||
background(255)
|
|
||||||
World.start
|
|
||||||
}
|
|
||||||
|
|
||||||
def draw() {
|
|
||||||
val world = World.snapshot
|
|
||||||
for (x <- 0 until Dim; y <- 0 until Dim; cell <- world(x)(y)) {
|
|
||||||
val (rx, ry, rw, rh) = (x * scale, y * scale, scale, scale)
|
|
||||||
noStroke()
|
|
||||||
fill(255)
|
|
||||||
rect(rx, ry, rw, rh)
|
|
||||||
if (cell.pher > 0) fill(0, 255, 0, cell.pher * PherScale)
|
|
||||||
if (cell.food > 0) fill(255, 0, 0, 255 * (cell.food / FoodRange.floatValue))
|
|
||||||
rect(rx, ry, rw, rh)
|
|
||||||
for (ant <- cell.ant) {
|
|
||||||
if (ant.food) stroke(255, 0, 0) else stroke(0)
|
|
||||||
val (hx, hy, tx, ty) = antLine(ant.dir)
|
|
||||||
line(rx + hx, ry + hy, rx + tx, ry + ty)
|
|
||||||
}
|
|
||||||
stroke(0, 0, 255)
|
|
||||||
noFill()
|
|
||||||
val homeStart = World.homeOff * scale
|
|
||||||
val homeWidth = AntsSqrt * scale
|
|
||||||
rect(homeStart, homeStart, homeWidth, homeWidth)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val s = scale - 1
|
|
||||||
val m = s / 2
|
|
||||||
|
|
||||||
def antLine(dir: Int) = dir match {
|
|
||||||
case 0|4 => (m, 0, m, s)
|
|
||||||
case 1|5 => (s, 0, 0, s)
|
|
||||||
case 2|6 => (s, m, 0, m)
|
|
||||||
case _ => (s, s, 0, 0)
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
####################
|
|
||||||
# Akka Config File #
|
|
||||||
####################
|
|
||||||
|
|
||||||
akka {
|
|
||||||
version = "2.0-SNAPSHOT"
|
|
||||||
|
|
||||||
enabled-modules = ["camel", "http"]
|
|
||||||
|
|
||||||
time-unit = "seconds"
|
|
||||||
|
|
||||||
event-handlers = ["akka.event.EventHandler$DefaultListener"]
|
|
||||||
|
|
||||||
boot = ["sample.camel.Boot"]
|
|
||||||
|
|
||||||
http {
|
|
||||||
hostname = "localhost"
|
|
||||||
port = 9998
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
<?xml version="1.0"?>
|
|
||||||
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
|
|
||||||
|
|
||||||
<Configure id="Server" class="org.eclipse.jetty.server.Server">
|
|
||||||
|
|
||||||
<!-- =========================================================== -->
|
|
||||||
<!-- Server Thread Pool -->
|
|
||||||
<!-- =========================================================== -->
|
|
||||||
<Set name="ThreadPool">
|
|
||||||
<New class="org.eclipse.jetty.util.thread.ExecutorThreadPool">
|
|
||||||
</New>
|
|
||||||
</Set>
|
|
||||||
|
|
||||||
<!-- =========================================================== -->
|
|
||||||
<!-- Set connectors -->
|
|
||||||
<!-- =========================================================== -->
|
|
||||||
|
|
||||||
<Call name="addConnector">
|
|
||||||
<Arg>
|
|
||||||
<New class="org.eclipse.jetty.server.nio.SelectChannelConnector">
|
|
||||||
<Set name="host"><SystemProperty name="jetty.host" /></Set>
|
|
||||||
<Set name="port"><SystemProperty name="jetty.port" default="8080"/></Set>
|
|
||||||
<Set name="maxIdleTime">300000</Set>
|
|
||||||
<Set name="Acceptors">2</Set>
|
|
||||||
<Set name="statsOn">false</Set>
|
|
||||||
<Set name="confidentialPort">8443</Set>
|
|
||||||
<Set name="lowResourcesConnections">20000</Set>
|
|
||||||
<Set name="lowResourcesMaxIdleTime">5000</Set>
|
|
||||||
</New>
|
|
||||||
</Arg>
|
|
||||||
</Call>
|
|
||||||
|
|
||||||
<!-- =========================================================== -->
|
|
||||||
<!-- Set handler -->
|
|
||||||
<!-- =========================================================== -->
|
|
||||||
<Set name="handler">
|
|
||||||
<New id="Handlers" class="org.eclipse.jetty.server.handler.HandlerCollection">
|
|
||||||
<Set name="handlers">
|
|
||||||
<Array type="org.eclipse.jetty.server.Handler">
|
|
||||||
<Item>
|
|
||||||
<New id="AkkaRestHandler" class="org.eclipse.jetty.servlet.ServletContextHandler">
|
|
||||||
<Set name="contextPath">/</Set>
|
|
||||||
<Call name="addServlet">
|
|
||||||
<Arg>akka.http.AkkaRestServlet</Arg>
|
|
||||||
<Arg>/*</Arg>
|
|
||||||
</Call>
|
|
||||||
</New>
|
|
||||||
</Item>
|
|
||||||
<Item>
|
|
||||||
<New id="DefaultHandler" class="org.eclipse.jetty.server.handler.DefaultHandler"/>
|
|
||||||
</Item>
|
|
||||||
</Array>
|
|
||||||
</Set>
|
|
||||||
</New>
|
|
||||||
</Set>
|
|
||||||
|
|
||||||
<!-- =========================================================== -->
|
|
||||||
<!-- extra options -->
|
|
||||||
<!-- =========================================================== -->
|
|
||||||
<Set name="stopAtShutdown">true</Set>
|
|
||||||
<Set name="sendServerVersion">true</Set>
|
|
||||||
<Set name="sendDateHeader">true</Set>
|
|
||||||
<Set name="gracefulShutdown">1000</Set>
|
|
||||||
|
|
||||||
</Configure>
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
package sample.camel;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Martin Krasser
|
|
||||||
*/
|
|
||||||
public class BeanImpl implements BeanIntf {
|
|
||||||
|
|
||||||
public String foo(String s) {
|
|
||||||
return "hello " + s;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
package sample.camel;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Martin Krasser
|
|
||||||
*/
|
|
||||||
public interface BeanIntf {
|
|
||||||
|
|
||||||
public String foo(String s);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
package sample.camel;
|
|
||||||
|
|
||||||
import org.apache.camel.Body;
|
|
||||||
import org.apache.camel.Header;
|
|
||||||
|
|
||||||
import akka.camel.consume;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Martin Krasser
|
|
||||||
*/
|
|
||||||
public interface RemoteTypedConsumer1 {
|
|
||||||
|
|
||||||
@consume("jetty:http://localhost:6644/camel/remote-typed-actor-1")
|
|
||||||
public String foo(@Body String body, @Header("name") String header);
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
package sample.camel;
|
|
||||||
|
|
||||||
import akka.actor.TypedActor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Martin Krasser
|
|
||||||
*/
|
|
||||||
public class RemoteTypedConsumer1Impl implements RemoteTypedConsumer1 {
|
|
||||||
|
|
||||||
public String foo(String body, String header) {
|
|
||||||
return String.format("remote1: body=%s header=%s", body, header);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
package sample.camel;
|
|
||||||
|
|
||||||
import org.apache.camel.Body;
|
|
||||||
import org.apache.camel.Header;
|
|
||||||
import akka.camel.consume;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Martin Krasser
|
|
||||||
*/
|
|
||||||
public interface RemoteTypedConsumer2 {
|
|
||||||
|
|
||||||
@consume("jetty:http://localhost:6644/camel/remote-typed-actor-2")
|
|
||||||
public String foo(@Body String body, @Header("name") String header);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
package sample.camel;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Martin Krasser
|
|
||||||
*/
|
|
||||||
public class RemoteTypedConsumer2Impl implements RemoteTypedConsumer2 {
|
|
||||||
|
|
||||||
public String foo(String body, String header) {
|
|
||||||
return String.format("remote2: body=%s header=%s", body, header);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
package sample.camel;
|
|
||||||
|
|
||||||
import org.apache.camel.Body;
|
|
||||||
import org.apache.camel.Header;
|
|
||||||
|
|
||||||
import akka.camel.consume;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Martin Krasser
|
|
||||||
*/
|
|
||||||
public interface TypedConsumer1 {
|
|
||||||
@consume("file:data/input/typed-actor")
|
|
||||||
public void foo(String body);
|
|
||||||
|
|
||||||
@consume("jetty:http://0.0.0.0:8877/camel/typed-actor")
|
|
||||||
public String bar(@Body String body, @Header("name") String header);
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
package sample.camel;
|
|
||||||
|
|
||||||
import org.apache.camel.Body;
|
|
||||||
import org.apache.camel.Header;
|
|
||||||
|
|
||||||
import akka.actor.TypedActor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Martin Krasser
|
|
||||||
*/
|
|
||||||
public class TypedConsumer1Impl implements TypedConsumer1 {
|
|
||||||
|
|
||||||
public void foo(String body) {
|
|
||||||
System.out.println("Received message:");
|
|
||||||
System.out.println(body);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String bar(@Body String body, @Header("name") String header) {
|
|
||||||
return String.format("body=%s header=%s", body, header);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
package sample.camel;
|
|
||||||
|
|
||||||
import org.apache.camel.Body;
|
|
||||||
import org.apache.camel.Header;
|
|
||||||
import akka.camel.consume;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Martin Krasser
|
|
||||||
*/
|
|
||||||
public interface TypedConsumer2 {
|
|
||||||
|
|
||||||
@consume("direct:default")
|
|
||||||
public String foo(String body);
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
package sample.camel;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Martin Krasser
|
|
||||||
*/
|
|
||||||
public class TypedConsumer2Impl implements TypedConsumer2 {
|
|
||||||
|
|
||||||
public String foo(String body) {
|
|
||||||
return String.format("default: %s", body);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
package sample.camel;
|
|
||||||
|
|
||||||
import akka.camel.Message;
|
|
||||||
import akka.camel.UntypedConsumerActor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Martin Krasser
|
|
||||||
*/
|
|
||||||
public class UntypedConsumer1 extends UntypedConsumerActor {
|
|
||||||
|
|
||||||
public String getEndpointUri() {
|
|
||||||
return "direct:untyped-consumer-1";
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onReceive(Object message) {
|
|
||||||
Message msg = (Message)message;
|
|
||||||
String body = msg.getBodyAs(String.class);
|
|
||||||
sender.tell(String.format("received %s", body));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="
|
|
||||||
http://www.springframework.org/schema/beans
|
|
||||||
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
|
|
||||||
|
|
||||||
<!-- ================================================================== -->
|
|
||||||
<!-- Camel JMS component and ActiveMQ setup -->
|
|
||||||
<!-- ================================================================== -->
|
|
||||||
|
|
||||||
<bean id="jms" class="org.apache.camel.component.jms.JmsComponent">
|
|
||||||
<property name="configuration" ref="jmsConfig"/>
|
|
||||||
</bean>
|
|
||||||
|
|
||||||
<bean id="jmsConfig" class="org.apache.camel.component.jms.JmsConfiguration">
|
|
||||||
<property name="connectionFactory" ref="singleConnectionFactory"/>
|
|
||||||
</bean>
|
|
||||||
|
|
||||||
<bean id="singleConnectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory">
|
|
||||||
<property name="targetConnectionFactory" ref="jmsConnectionFactory"/>
|
|
||||||
</bean>
|
|
||||||
|
|
||||||
<bean id="jmsConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
|
|
||||||
<property name="brokerURL" value="vm://testbroker"/>
|
|
||||||
</bean>
|
|
||||||
|
|
||||||
</beans>
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xmlns:akka="http://akka.io/schema/akka"
|
|
||||||
xmlns:camel="http://camel.apache.org/schema/spring"
|
|
||||||
xsi:schemaLocation="
|
|
||||||
http://www.springframework.org/schema/beans
|
|
||||||
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
|
|
||||||
http://akka.io/schema/akka
|
|
||||||
http://akka.io/akka-2.0-SNAPSHOT.xsd
|
|
||||||
http://camel.apache.org/schema/spring
|
|
||||||
http://camel.apache.org/schema/spring/camel-spring.xsd">
|
|
||||||
|
|
||||||
<bean id="routeBuilder" class="sample.camel.StandaloneSpringApplicationRoute" />
|
|
||||||
|
|
||||||
<camel:camelContext id="camelContext">
|
|
||||||
<camel:routeBuilder ref="routeBuilder" />
|
|
||||||
</camel:camelContext>
|
|
||||||
|
|
||||||
<akka:camel-service id="service">
|
|
||||||
<akka:camel-context ref="camelContext" />
|
|
||||||
</akka:camel-service>
|
|
||||||
|
|
||||||
<akka:typed-actor id="ta" interface="sample.camel.BeanIntf" implementation="sample.camel.BeanImpl" timeout="1000" />
|
|
||||||
<akka:untyped-actor id="ua" implementation="sample.camel.UntypedConsumer1" scope="singleton" autostart="true" />
|
|
||||||
|
|
||||||
</beans>
|
|
||||||
|
|
@ -1,161 +0,0 @@
|
||||||
package sample.camel
|
|
||||||
|
|
||||||
import org.apache.camel.Exchange
|
|
||||||
|
|
||||||
import akka.actor.{ Actor, ActorRef, ActorRegistry }
|
|
||||||
import akka.camel.{ Ack, Failure, Producer, Message, Consumer }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Client-initiated remote actor.
|
|
||||||
*/
|
|
||||||
class RemoteActor1 extends Actor with Consumer {
|
|
||||||
def endpointUri = "jetty:http://localhost:6644/camel/remote-actor-1"
|
|
||||||
|
|
||||||
protected def receive = {
|
|
||||||
case msg: Message ⇒ sender ! Message("hello %s" format msg.bodyAs[String], Map("sender" -> "remote1"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Server-initiated remote actor.
|
|
||||||
*/
|
|
||||||
class RemoteActor2 extends Actor with Consumer {
|
|
||||||
def endpointUri = "jetty:http://localhost:6644/camel/remote-actor-2"
|
|
||||||
|
|
||||||
protected def receive = {
|
|
||||||
case msg: Message ⇒ sender ! Message("hello %s" format msg.bodyAs[String], Map("sender" -> "remote2"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Producer1 extends Actor with Producer {
|
|
||||||
def endpointUri = "direct:welcome"
|
|
||||||
override def oneway = false // default
|
|
||||||
}
|
|
||||||
|
|
||||||
class Consumer1 extends Actor with Consumer {
|
|
||||||
def endpointUri = "file:data/input/actor"
|
|
||||||
|
|
||||||
def receive = {
|
|
||||||
case msg: Message ⇒ println("received %s" format msg.bodyAs[String])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Consumer2 extends Actor with Consumer {
|
|
||||||
def endpointUri = "jetty:http://0.0.0.0:8877/camel/default"
|
|
||||||
|
|
||||||
def receive = {
|
|
||||||
case msg: Message ⇒ sender ! ("Hello %s" format msg.bodyAs[String])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Consumer3(transformer: ActorRef) extends Actor with Consumer {
|
|
||||||
def endpointUri = "jetty:http://0.0.0.0:8877/camel/welcome"
|
|
||||||
|
|
||||||
def receive = {
|
|
||||||
case msg: Message ⇒ transformer.forward(msg.setBodyAs[String])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Consumer4 extends Actor with Consumer {
|
|
||||||
def endpointUri = "jetty:http://0.0.0.0:8877/camel/stop"
|
|
||||||
|
|
||||||
def receive = {
|
|
||||||
case msg: Message ⇒ msg.bodyAs[String] match {
|
|
||||||
case "stop" ⇒ {
|
|
||||||
sender ! "Consumer4 stopped"
|
|
||||||
self.stop
|
|
||||||
}
|
|
||||||
case body ⇒ sender ! body
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Consumer5 extends Actor with Consumer {
|
|
||||||
def endpointUri = "jetty:http://0.0.0.0:8877/camel/start"
|
|
||||||
|
|
||||||
def receive = {
|
|
||||||
case _ ⇒ {
|
|
||||||
Actor.actorOf[Consumer4]
|
|
||||||
sender ! "Consumer4 started"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Transformer(producer: ActorRef) extends Actor {
|
|
||||||
protected def receive = {
|
|
||||||
case msg: Message ⇒ producer.forward(msg.transformBody((body: String) ⇒ "- %s -" format body))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Subscriber(name: String, uri: String) extends Actor with Consumer {
|
|
||||||
def endpointUri = uri
|
|
||||||
|
|
||||||
protected def receive = {
|
|
||||||
case msg: Message ⇒ println("%s received: %s" format (name, msg.body))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Publisher(uri: String) extends Actor with Producer {
|
|
||||||
def endpointUri = uri
|
|
||||||
override def oneway = true
|
|
||||||
}
|
|
||||||
|
|
||||||
class PublisherBridge(uri: String, publisher: ActorRef) extends Actor with Consumer {
|
|
||||||
def endpointUri = uri
|
|
||||||
|
|
||||||
protected def receive = {
|
|
||||||
case msg: Message ⇒ {
|
|
||||||
publisher ! msg.bodyAs[String]
|
|
||||||
sender ! "message published"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class HttpConsumer(producer: ActorRef) extends Actor with Consumer {
|
|
||||||
def endpointUri = "jetty:http://0.0.0.0:8875/"
|
|
||||||
|
|
||||||
protected def receive = {
|
|
||||||
case msg ⇒ producer forward msg
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class HttpProducer(transformer: ActorRef) extends Actor with Producer {
|
|
||||||
def endpointUri = "jetty://http://akka.io/?bridgeEndpoint=true"
|
|
||||||
|
|
||||||
override protected def receiveBeforeProduce = {
|
|
||||||
// only keep Exchange.HTTP_PATH message header (which needed by bridge endpoint)
|
|
||||||
case msg: Message ⇒ msg.setHeaders(msg.headers(Set(Exchange.HTTP_PATH)))
|
|
||||||
}
|
|
||||||
|
|
||||||
override protected def receiveAfterProduce = {
|
|
||||||
// do not reply but forward result to transformer
|
|
||||||
case msg ⇒ transformer forward msg
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class HttpTransformer extends Actor {
|
|
||||||
protected def receive = {
|
|
||||||
case msg: Message ⇒ sender ! (msg.transformBody { body: String ⇒ body replaceAll ("Akka ", "AKKA ") })
|
|
||||||
case msg: Failure ⇒ sender ! msg
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class FileConsumer extends Actor with Consumer {
|
|
||||||
def endpointUri = "file:data/input/actor?delete=true"
|
|
||||||
override def autoack = false
|
|
||||||
|
|
||||||
var counter = 0
|
|
||||||
|
|
||||||
def receive = {
|
|
||||||
case msg: Message ⇒ {
|
|
||||||
if (counter == 2) {
|
|
||||||
println("received %s" format msg.bodyAs[String])
|
|
||||||
sender ! Ack
|
|
||||||
} else {
|
|
||||||
println("rejected %s" format msg.bodyAs[String])
|
|
||||||
counter += 1
|
|
||||||
sender ! Failure(new Exception("message number %s not accepted" format counter))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
package sample.camel
|
|
||||||
|
|
||||||
import org.apache.camel.{ Exchange, Processor }
|
|
||||||
import org.apache.camel.builder.RouteBuilder
|
|
||||||
import org.apache.camel.impl.DefaultCamelContext
|
|
||||||
import org.apache.camel.spring.spi.ApplicationContextRegistry
|
|
||||||
import org.springframework.context.support.ClassPathXmlApplicationContext
|
|
||||||
|
|
||||||
import akka.actor.Actor._
|
|
||||||
import akka.actor.Props
|
|
||||||
import akka.actor.TypedActor
|
|
||||||
import akka.camel.CamelContextManager
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Martin Krasser
|
|
||||||
*/
|
|
||||||
class Boot {
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
// Basic example
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
|
|
||||||
actorOf[Consumer1]
|
|
||||||
actorOf[Consumer2]
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
// Custom Camel route example
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
|
|
||||||
// Create CamelContext and a Spring-based registry
|
|
||||||
val context = new ClassPathXmlApplicationContext("/context-jms.xml", getClass)
|
|
||||||
val registry = new ApplicationContextRegistry(context)
|
|
||||||
|
|
||||||
// Use a custom Camel context and a custom touter builder
|
|
||||||
CamelContextManager.init(new DefaultCamelContext(registry))
|
|
||||||
CamelContextManager.mandatoryContext.addRoutes(new CustomRouteBuilder)
|
|
||||||
|
|
||||||
val producer = actorOf[Producer1]
|
|
||||||
val mediator = actorOf(new Transformer(producer))
|
|
||||||
val consumer = actorOf(new Consumer3(mediator))
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
// Asynchronous consumer-producer example (Akka homepage transformation)
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
|
|
||||||
val httpTransformer = actorOf(new HttpTransformer)
|
|
||||||
val httpProducer = actorOf(new HttpProducer(httpTransformer))
|
|
||||||
val httpConsumer = actorOf(new HttpConsumer(httpProducer))
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
// Publish subscribe examples
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
|
|
||||||
//
|
|
||||||
// Cometd example commented out because camel-cometd is broken since Camel 2.3
|
|
||||||
//
|
|
||||||
|
|
||||||
//val cometdUri = "cometd://localhost:8111/test/abc?baseResource=file:target"
|
|
||||||
//val cometdSubscriber = actorOf(new Subscriber("cometd-subscriber", cometdUri))
|
|
||||||
//val cometdPublisher = actorOf(new Publisher("cometd-publisher", cometdUri))
|
|
||||||
|
|
||||||
val jmsUri = "jms:topic:test"
|
|
||||||
val jmsSubscriber1 = actorOf(new Subscriber("jms-subscriber-1", jmsUri))
|
|
||||||
val jmsSubscriber2 = actorOf(new Subscriber("jms-subscriber-2", jmsUri))
|
|
||||||
val jmsPublisher = actorOf(new Publisher(jmsUri), "jms-publisher")
|
|
||||||
|
|
||||||
//val cometdPublisherBridge = actorOf(new PublisherBridge("jetty:http://0.0.0.0:8877/camel/pub/cometd", cometdPublisher))
|
|
||||||
val jmsPublisherBridge = actorOf(new PublisherBridge("jetty:http://0.0.0.0:8877/camel/pub/jms", jmsPublisher))
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
// Actor un-publishing and re-publishing example
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
|
|
||||||
actorOf[Consumer4] // POSTing "stop" to http://0.0.0.0:8877/camel/stop stops and unpublishes this actor
|
|
||||||
actorOf[Consumer5] // POSTing any msg to http://0.0.0.0:8877/camel/start starts and published Consumer4 again.
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
// Active object example
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
|
|
||||||
// TODO: investigate why this consumer is not published
|
|
||||||
TypedActor.typedActorOf(classOf[TypedConsumer1], classOf[TypedConsumer1Impl], Props())
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Martin Krasser
|
|
||||||
*/
|
|
||||||
class CustomRouteBuilder extends RouteBuilder {
|
|
||||||
def configure {
|
|
||||||
val actorUri = "actor:%s" format classOf[Consumer2].getName
|
|
||||||
from("jetty:http://0.0.0.0:8877/camel/custom").to(actorUri)
|
|
||||||
from("direct:welcome").process(new Processor() {
|
|
||||||
def process(exchange: Exchange) {
|
|
||||||
exchange.getOut.setBody("Welcome %s" format exchange.getIn.getBody)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
package sample.camel
|
|
||||||
|
|
||||||
import akka.actor.Actor._
|
|
||||||
import akka.actor.TypedActor
|
|
||||||
import akka.camel.Message
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Martin Krasser
|
|
||||||
*/
|
|
||||||
object ClientApplication extends App {
|
|
||||||
|
|
||||||
/* TODO: fix remote example
|
|
||||||
|
|
||||||
val actor1 = remote.actorOf[RemoteActor1]("localhost", 7777)
|
|
||||||
val actor2 = remote.actorFor("remote2", "localhost", 7777)
|
|
||||||
|
|
||||||
val typedActor1 =
|
|
||||||
TypedActor.newRemoteInstance(classOf[RemoteTypedConsumer1],classOf[RemoteTypedConsumer1Impl], "localhost", 7777)
|
|
||||||
|
|
||||||
val typedActor2 = remote.typedActorFor(classOf[RemoteTypedConsumer2], "remote3", "localhost", 7777)
|
|
||||||
|
|
||||||
println(actor1 !! Message("actor1")) // activates and publishes actor remotely
|
|
||||||
println(actor2 !! Message("actor2")) // actor already activated and published remotely
|
|
||||||
|
|
||||||
println(typedActor1.foo("x1", "y1")) // activates and publishes typed actor methods remotely
|
|
||||||
println(typedActor2.foo("x2", "y2")) // typed actor methods already activated and published remotely
|
|
||||||
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
package sample.camel
|
|
||||||
|
|
||||||
import akka.actor.Actor._
|
|
||||||
import akka.camel.CamelServiceManager
|
|
||||||
import akka.actor.{ TypedActor, Props }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Martin Krasser
|
|
||||||
*/
|
|
||||||
object ServerApplication extends App {
|
|
||||||
import CamelServiceManager._
|
|
||||||
|
|
||||||
/* TODO: fix remote example
|
|
||||||
|
|
||||||
startCamelService
|
|
||||||
|
|
||||||
val ua = actorOf[RemoteActor2]
|
|
||||||
val ta = TypedActor.typedActorOf(
|
|
||||||
classOf[RemoteTypedConsumer2],
|
|
||||||
classOf[RemoteTypedConsumer2Impl], Props())
|
|
||||||
|
|
||||||
remote.start("localhost", 7777)
|
|
||||||
remote.register("remote2", ua)
|
|
||||||
remote.registerTypedActor("remote3", ta)
|
|
||||||
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
@ -1,128 +0,0 @@
|
||||||
package sample.camel
|
|
||||||
|
|
||||||
import org.apache.camel.impl.{ DefaultCamelContext, SimpleRegistry }
|
|
||||||
import org.apache.camel.builder.RouteBuilder
|
|
||||||
import org.apache.camel.spring.spi.ApplicationContextRegistry
|
|
||||||
import org.springframework.context.support.ClassPathXmlApplicationContext
|
|
||||||
|
|
||||||
import akka.actor.{ Actor, TypedActor, Props }
|
|
||||||
import akka.camel._
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Martin Krasser
|
|
||||||
*/
|
|
||||||
object StandaloneApplication extends App {
|
|
||||||
import CamelContextManager._
|
|
||||||
import CamelServiceManager._
|
|
||||||
|
|
||||||
// 'externally' register typed actors
|
|
||||||
val registry = new SimpleRegistry
|
|
||||||
registry.put("sample", TypedActor.typedActorOf(classOf[BeanIntf], classOf[BeanImpl], Props()))
|
|
||||||
|
|
||||||
// customize CamelContext
|
|
||||||
CamelContextManager.init(new DefaultCamelContext(registry))
|
|
||||||
CamelContextManager.mandatoryContext.addRoutes(new StandaloneApplicationRoute)
|
|
||||||
|
|
||||||
startCamelService
|
|
||||||
|
|
||||||
// access 'externally' registered typed actors
|
|
||||||
assert("hello msg1" == mandatoryContext.createProducerTemplate.requestBody("direct:test", "msg1"))
|
|
||||||
|
|
||||||
mandatoryService.awaitEndpointActivation(1) {
|
|
||||||
// 'internally' register typed actor (requires CamelService)
|
|
||||||
TypedActor.typedActorOf(classOf[TypedConsumer2], classOf[TypedConsumer2Impl], Props())
|
|
||||||
}
|
|
||||||
|
|
||||||
// access 'internally' (automatically) registered typed-actors
|
|
||||||
// (see @consume annotation value at TypedConsumer2.foo method)
|
|
||||||
assert("default: msg3" == mandatoryContext.createProducerTemplate.requestBody("direct:default", "msg3"))
|
|
||||||
|
|
||||||
stopCamelService
|
|
||||||
|
|
||||||
Actor.registry.local.shutdownAll
|
|
||||||
}
|
|
||||||
|
|
||||||
class StandaloneApplicationRoute extends RouteBuilder {
|
|
||||||
def configure = {
|
|
||||||
// route to typed actors (in SimpleRegistry)
|
|
||||||
from("direct:test").to("typed-actor:sample?method=foo")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object StandaloneSpringApplication extends App {
|
|
||||||
import CamelContextManager._
|
|
||||||
|
|
||||||
// load Spring application context
|
|
||||||
val appctx = new ClassPathXmlApplicationContext("/context-standalone.xml")
|
|
||||||
|
|
||||||
// We cannot use the CamelServiceManager to wait for endpoint activation
|
|
||||||
// because CamelServiceManager is started by the Spring application context.
|
|
||||||
// (and hence is not available for setting expectations on activations). This
|
|
||||||
// will be improved/enabled in upcoming releases.
|
|
||||||
Thread.sleep(1000)
|
|
||||||
|
|
||||||
// access 'externally' registered typed actors with typed-actor component
|
|
||||||
assert("hello msg3" == mandatoryTemplate.requestBody("direct:test3", "msg3"))
|
|
||||||
|
|
||||||
// access auto-started untyped consumer
|
|
||||||
assert("received msg3" == mandatoryTemplate.requestBody("direct:untyped-consumer-1", "msg3"))
|
|
||||||
|
|
||||||
appctx.close
|
|
||||||
|
|
||||||
Actor.registry.local.shutdownAll
|
|
||||||
}
|
|
||||||
|
|
||||||
class StandaloneSpringApplicationRoute extends RouteBuilder {
|
|
||||||
def configure = {
|
|
||||||
// routes to typed actor (in ApplicationContextRegistry)
|
|
||||||
from("direct:test3").to("typed-actor:ta?method=foo")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object StandaloneJmsApplication extends App {
|
|
||||||
import CamelServiceManager._
|
|
||||||
|
|
||||||
val context = new ClassPathXmlApplicationContext("/context-jms.xml")
|
|
||||||
val registry = new ApplicationContextRegistry(context)
|
|
||||||
|
|
||||||
// Init CamelContextManager with custom CamelContext
|
|
||||||
CamelContextManager.init(new DefaultCamelContext(registry))
|
|
||||||
|
|
||||||
startCamelService
|
|
||||||
|
|
||||||
val jmsUri = "jms:topic:test"
|
|
||||||
val jmsPublisher = Actor.actorOf(new Publisher(jmsUri), "jms-publisher")
|
|
||||||
|
|
||||||
mandatoryService.awaitEndpointActivation(2) {
|
|
||||||
Actor.actorOf(new Subscriber("jms-subscriber-1", jmsUri))
|
|
||||||
Actor.actorOf(new Subscriber("jms-subscriber-2", jmsUri))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send 10 messages to via publisher actor
|
|
||||||
for (i ← 1 to 10) {
|
|
||||||
jmsPublisher ! ("Akka rocks (%d)" format i)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send 10 messages to JMS topic directly
|
|
||||||
for (i ← 1 to 10) {
|
|
||||||
CamelContextManager.mandatoryTemplate.sendBody(jmsUri, "Camel rocks (%d)" format i)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait a bit for subscribes to receive messages
|
|
||||||
Thread.sleep(1000)
|
|
||||||
|
|
||||||
stopCamelService
|
|
||||||
Actor.registry.local.shutdownAll
|
|
||||||
}
|
|
||||||
|
|
||||||
object StandaloneFileApplication {
|
|
||||||
import CamelServiceManager._
|
|
||||||
|
|
||||||
def main(args: Array[String]) {
|
|
||||||
startCamelService
|
|
||||||
mandatoryService.awaitEndpointActivation(1) {
|
|
||||||
Actor.actorOf(new FileConsumer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
package sample.camel;
|
|
||||||
|
|
||||||
import akka.camel.Message;
|
|
||||||
import akka.camel.UntypedConsumerActor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Martin Krasser
|
|
||||||
*/
|
|
||||||
public class SampleRemoteUntypedConsumer extends UntypedConsumerActor {
|
|
||||||
public String getEndpointUri() {
|
|
||||||
return "direct:remote-untyped-consumer";
|
|
||||||
}
|
|
||||||
|
|
||||||
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,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>
|
|
||||||
|
|
@ -1,99 +0,0 @@
|
||||||
package sample.camel
|
|
||||||
|
|
||||||
import _root_.akka.routing.{ RoutedProps, Routing }
|
|
||||||
import collection.mutable.Set
|
|
||||||
|
|
||||||
import java.util.concurrent.CountDownLatch
|
|
||||||
|
|
||||||
import org.junit._
|
|
||||||
import org.scalatest.junit.JUnitSuite
|
|
||||||
|
|
||||||
import akka.actor.Actor._
|
|
||||||
import akka.actor.{ ActorRegistry, ActorRef, Actor }
|
|
||||||
import akka.camel._
|
|
||||||
import akka.camel.CamelServiceManager._
|
|
||||||
/**
|
|
||||||
* @author Martin Krasser
|
|
||||||
*/
|
|
||||||
class HttpConcurrencyTestStress extends JUnitSuite {
|
|
||||||
import HttpConcurrencyTestStress._
|
|
||||||
|
|
||||||
@Test
|
|
||||||
def shouldProcessMessagesConcurrently = {
|
|
||||||
/* TODO: fix stress test
|
|
||||||
|
|
||||||
val num = 50
|
|
||||||
val latch1 = new CountDownLatch(num)
|
|
||||||
val latch2 = new CountDownLatch(num)
|
|
||||||
val latch3 = new CountDownLatch(num)
|
|
||||||
val client1 = actorOf(new HttpClientActor("client1", latch1))
|
|
||||||
val client2 = actorOf(new HttpClientActor("client2", latch2))
|
|
||||||
val client3 = actorOf(new HttpClientActor("client3", latch3))
|
|
||||||
for (i <- 1 to num) {
|
|
||||||
client1 ! Message("client1", Map(Message.MessageExchangeId -> i))
|
|
||||||
client2 ! Message("client2", Map(Message.MessageExchangeId -> i))
|
|
||||||
client3 ! Message("client3", Map(Message.MessageExchangeId -> i))
|
|
||||||
}
|
|
||||||
latch1.await
|
|
||||||
latch2.await
|
|
||||||
latch3.await
|
|
||||||
assert(num == (client1 ? "getCorrelationIdCount").as[Int].get)
|
|
||||||
assert(num == (client2 ? "getCorrelationIdCount").as[Int].get)
|
|
||||||
assert(num == (client3 ? "getCorrelationIdCount").as[Int].get)*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object HttpConcurrencyTestStress {
|
|
||||||
@BeforeClass
|
|
||||||
def beforeClass{
|
|
||||||
startCamelService
|
|
||||||
|
|
||||||
val workers = for (i ← 1 to 8) yield actorOf[HttpServerWorker]
|
|
||||||
val balancer = Routing.actorOf(RoutedProps().withRoundRobinRouter.withConnections(workers), "loadbalancer")
|
|
||||||
//service.get.awaitEndpointActivation(1) {
|
|
||||||
// actorOf(new HttpServerActor(balancer))
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
|
|
||||||
@AfterClass
|
|
||||||
def afterClass = {
|
|
||||||
stopCamelService
|
|
||||||
Actor.registry.local.shutdownAll
|
|
||||||
}
|
|
||||||
|
|
||||||
class HttpClientActor(label: String, latch: CountDownLatch) extends Actor with Producer {
|
|
||||||
def endpointUri = "jetty:http://0.0.0.0:8855/echo"
|
|
||||||
var correlationIds = Set[Any]()
|
|
||||||
|
|
||||||
override protected def receive = {
|
|
||||||
case "getCorrelationIdCount" ⇒ sender ! correlationIds.size
|
|
||||||
case msg ⇒ super.receive(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
override protected def receiveAfterProduce = {
|
|
||||||
case msg: Message ⇒ {
|
|
||||||
val corr = msg.headers(Message.MessageExchangeId)
|
|
||||||
val body = msg.bodyAs[String]
|
|
||||||
correlationIds += corr
|
|
||||||
assert(label == body)
|
|
||||||
latch.countDown
|
|
||||||
print(".")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class HttpServerActor(balancer: ActorRef) extends Actor with Consumer {
|
|
||||||
def endpointUri = "jetty:http://0.0.0.0:8855/echo"
|
|
||||||
var counter = 0
|
|
||||||
|
|
||||||
def receive = {
|
|
||||||
case msg ⇒ balancer forward msg
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class HttpServerWorker extends Actor {
|
|
||||||
protected def receive = {
|
|
||||||
case msg ⇒ sender ! msg
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,101 +0,0 @@
|
||||||
package sample.camel
|
|
||||||
|
|
||||||
import org.scalatest.{ GivenWhenThen, BeforeAndAfterAll, FeatureSpec }
|
|
||||||
|
|
||||||
import akka.actor.Actor._
|
|
||||||
import akka.actor._
|
|
||||||
import akka.camel._
|
|
||||||
//import akka.cluster.netty.NettyRemoteSupport
|
|
||||||
//import akka.cluster.RemoteServerModule
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Martin Krasser
|
|
||||||
*/
|
|
||||||
class RemoteConsumerTest /*extends FeatureSpec with BeforeAndAfterAll with GivenWhenThen*/ {
|
|
||||||
/* TODO: fix remote test
|
|
||||||
|
|
||||||
import CamelServiceManager._
|
|
||||||
import RemoteConsumerTest._
|
|
||||||
|
|
||||||
var server: RemoteServerModule = _
|
|
||||||
|
|
||||||
override protected def beforeAll = {
|
|
||||||
registry.shutdownAll
|
|
||||||
|
|
||||||
startCamelService
|
|
||||||
|
|
||||||
remote.shutdown
|
|
||||||
remote.asInstanceOf[NettyRemoteSupport].optimizeLocal.set(false)
|
|
||||||
|
|
||||||
server = remote.start(host,port)
|
|
||||||
}
|
|
||||||
|
|
||||||
override protected def afterAll = {
|
|
||||||
remote.shutdown
|
|
||||||
|
|
||||||
stopCamelService
|
|
||||||
|
|
||||||
registry.shutdownAll
|
|
||||||
remote.asInstanceOf[NettyRemoteSupport].optimizeLocal.set(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
feature("Publish consumer on remote node") {
|
|
||||||
scenario("access published remote consumer") {
|
|
||||||
given("a consumer actor")
|
|
||||||
val consumer = Actor.actorOf[RemoteConsumer]
|
|
||||||
|
|
||||||
when("registered at the server")
|
|
||||||
assert(mandatoryService.awaitEndpointActivation(1) {
|
|
||||||
remote.register(consumer)
|
|
||||||
})
|
|
||||||
|
|
||||||
then("the published consumer is accessible via its endpoint URI")
|
|
||||||
val response = CamelContextManager.mandatoryTemplate.requestBody("direct:remote-consumer", "test")
|
|
||||||
assert(response === "remote actor: test")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
feature("Publish typed consumer on remote node") {
|
|
||||||
scenario("access published remote consumer method") {
|
|
||||||
given("a typed consumer actor")
|
|
||||||
when("registered at the server")
|
|
||||||
assert(mandatoryService.awaitEndpointActivation(1) {
|
|
||||||
remote.registerTypedActor("whatever", TypedActor.newInstance(
|
|
||||||
classOf[SampleRemoteTypedConsumer],
|
|
||||||
classOf[SampleRemoteTypedConsumerImpl]))
|
|
||||||
})
|
|
||||||
then("the published method is accessible via its endpoint URI")
|
|
||||||
val response = CamelContextManager.mandatoryTemplate.requestBody("direct:remote-typed-consumer", "test")
|
|
||||||
assert(response === "remote typed actor: test")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
feature("Publish untyped consumer on remote node") {
|
|
||||||
scenario("access published remote untyped consumer") {
|
|
||||||
given("an untyped consumer actor")
|
|
||||||
val consumer = Actor.actorOf(classOf[SampleRemoteUntypedConsumer])
|
|
||||||
|
|
||||||
when("registered at the server")
|
|
||||||
assert(mandatoryService.awaitEndpointActivation(1) {
|
|
||||||
remote.register(consumer)
|
|
||||||
})
|
|
||||||
then("the published untyped consumer is accessible via its endpoint URI")
|
|
||||||
val response = CamelContextManager.mandatoryTemplate.requestBodyAndHeader("direct:remote-untyped-consumer", "a", "test", "b")
|
|
||||||
assert(response === "a b")
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
|
|
||||||
object RemoteConsumerTest {
|
|
||||||
val host = "localhost"
|
|
||||||
val port = 7774
|
|
||||||
|
|
||||||
class RemoteConsumer extends Actor with Consumer {
|
|
||||||
def endpointUri = "direct:remote-consumer"
|
|
||||||
|
|
||||||
protected def receive = {
|
|
||||||
case "init" ⇒ sender ! "done"
|
|
||||||
case m: Message ⇒ sender ! ("remote actor: %s" format m.body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
Akka Chat Client/Server Sample Application
|
|
||||||
|
|
||||||
How to run the sample:
|
|
||||||
|
|
||||||
1. Fire up two shells. For each of them:
|
|
||||||
- Step down into to the root of the Akka distribution.
|
|
||||||
- Set 'export AKKA_HOME=<root of distribution>.
|
|
||||||
- Run 'sbt console' to start up a REPL (interpreter).
|
|
||||||
2. In the first REPL you get execute:
|
|
||||||
- scala> import sample.chat._
|
|
||||||
- scala> import akka.actor.Actor._
|
|
||||||
- scala> val chatService = actorOf[ChatService]
|
|
||||||
3. In the second REPL you get execute:
|
|
||||||
- scala> import sample.chat._
|
|
||||||
- scala> ClientRunner.run
|
|
||||||
4. See the chat simulation run.
|
|
||||||
5. Run it again to see full speed after first initialization.
|
|
||||||
6. In the client REPL, or in a new REPL, you can also create your own client
|
|
||||||
- scala> import sample.chat._
|
|
||||||
- scala> val myClient = new ChatClient("<your name>")
|
|
||||||
- scala> myClient.login
|
|
||||||
- scala> myClient.post("Can I join?")
|
|
||||||
- scala> println("CHAT LOG:\n\t" + myClient.chatLog.log.mkString("\n\t"))
|
|
||||||
|
|
||||||
That’s it. Have fun.
|
|
||||||
|
|
||||||
|
|
@ -1,253 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (C) 2009-2010 Typesafe Inc. <http://www.typesafe.com>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package sample.chat
|
|
||||||
|
|
||||||
import scala.collection.mutable.HashMap
|
|
||||||
|
|
||||||
import akka.actor.{Actor, ActorRef, Props}
|
|
||||||
import akka.stm._
|
|
||||||
import akka.actor.Actor._
|
|
||||||
import akka.event.EventHandler
|
|
||||||
|
|
||||||
/******************************************************************************
|
|
||||||
Akka Chat Client/Server Sample Application
|
|
||||||
|
|
||||||
How to run the sample:
|
|
||||||
|
|
||||||
1. Fire up two shells. For each of them:
|
|
||||||
- Step down into to the root of the Akka distribution.
|
|
||||||
- Set 'export AKKA_HOME=<root of distribution>.
|
|
||||||
- Run 'sbt console' to start up a REPL (interpreter).
|
|
||||||
2. In the first REPL you get execute:
|
|
||||||
- scala> import sample.chat._
|
|
||||||
- scala> import akka.actor.Actor._
|
|
||||||
- scala> val chatService = actorOf[ChatService]
|
|
||||||
3. In the second REPL you get execute:
|
|
||||||
- scala> import sample.chat._
|
|
||||||
- scala> ClientRunner.run
|
|
||||||
4. See the chat simulation run.
|
|
||||||
5. Run it again to see full speed after first initialization.
|
|
||||||
6. In the client REPL, or in a new REPL, you can also create your own client
|
|
||||||
- scala> import sample.chat._
|
|
||||||
- scala> val myClient = new ChatClient("<your name>")
|
|
||||||
- scala> myClient.login
|
|
||||||
- scala> myClient.post("Can I join?")
|
|
||||||
- scala> println("CHAT LOG:\n\t" + myClient.chatLog.log.mkString("\n\t"))
|
|
||||||
|
|
||||||
|
|
||||||
That’s it. Have fun.
|
|
||||||
|
|
||||||
******************************************************************************/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ChatServer's internal events.
|
|
||||||
*/
|
|
||||||
sealed trait Event
|
|
||||||
case class Login(user: String) extends Event
|
|
||||||
case class Logout(user: String) extends Event
|
|
||||||
case class GetChatLog(from: String) extends Event
|
|
||||||
case class ChatLog(log: List[String]) extends Event
|
|
||||||
case class ChatMessage(from: String, message: String) extends Event
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Chat client.
|
|
||||||
*/
|
|
||||||
class ChatClient(val name: String) {
|
|
||||||
val chat = Actor.remote.actorFor("chat:service", "localhost", 2552)
|
|
||||||
|
|
||||||
def login = chat ! Login(name)
|
|
||||||
def logout = chat ! Logout(name)
|
|
||||||
def post(message: String) = chat ! ChatMessage(name, name + ": " + message)
|
|
||||||
def chatLog = (chat !! GetChatLog(name)).as[ChatLog].getOrElse(throw new Exception("Couldn't get the chat log from ChatServer"))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal chat client session.
|
|
||||||
*/
|
|
||||||
class Session(user: String, storage: ActorRef) extends Actor {
|
|
||||||
private val loginTime = System.currentTimeMillis
|
|
||||||
private var userLog: List[String] = Nil
|
|
||||||
|
|
||||||
EventHandler.info(this, "New session for user [%s] has been created at [%s]".format(user, loginTime))
|
|
||||||
|
|
||||||
def receive = {
|
|
||||||
case msg @ ChatMessage(from, message) =>
|
|
||||||
userLog ::= message
|
|
||||||
storage ! msg
|
|
||||||
|
|
||||||
case msg @ GetChatLog(_) =>
|
|
||||||
storage forward msg
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Abstraction of chat storage holding the chat log.
|
|
||||||
*/
|
|
||||||
trait ChatStorage extends Actor
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Memory-backed chat storage implementation.
|
|
||||||
*/
|
|
||||||
class MemoryChatStorage extends ChatStorage {
|
|
||||||
private var chatLog = TransactionalVector[Array[Byte]]()
|
|
||||||
|
|
||||||
EventHandler.info(this, "Memory-based chat storage is starting up...")
|
|
||||||
|
|
||||||
def receive = {
|
|
||||||
case msg @ ChatMessage(from, message) =>
|
|
||||||
EventHandler.debug(this, "New chat message [%s]".format(message))
|
|
||||||
atomic { chatLog + message.getBytes("UTF-8") }
|
|
||||||
|
|
||||||
case GetChatLog(_) =>
|
|
||||||
val messageList = atomic { chatLog.map(bytes => new String(bytes, "UTF-8")).toList }
|
|
||||||
reply(ChatLog(messageList))
|
|
||||||
}
|
|
||||||
|
|
||||||
override def postRestart(reason: Throwable) {
|
|
||||||
chatLog = TransactionalVector()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implements user session management.
|
|
||||||
* <p/>
|
|
||||||
* Uses self-type annotation (this: Actor =>) to declare that it needs to be mixed in with an Actor.
|
|
||||||
*/
|
|
||||||
trait SessionManagement { this: Actor =>
|
|
||||||
|
|
||||||
val storage: ActorRef // needs someone to provide the ChatStorage
|
|
||||||
val sessions = new HashMap[String, ActorRef]
|
|
||||||
|
|
||||||
protected def sessionManagement: Receive = {
|
|
||||||
case Login(username) =>
|
|
||||||
EventHandler.info(this, "User [%s] has logged in".format(username))
|
|
||||||
val session = actorOf(new Session(username, storage))
|
|
||||||
session
|
|
||||||
sessions += (username -> session)
|
|
||||||
|
|
||||||
case Logout(username) =>
|
|
||||||
EventHandler.info(this, "User [%s] has logged out".format(username))
|
|
||||||
val session = sessions(username)
|
|
||||||
session.stop()
|
|
||||||
sessions -= username
|
|
||||||
}
|
|
||||||
|
|
||||||
protected def shutdownSessions() {
|
|
||||||
sessions.foreach { case (_, session) => session.stop() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implements chat management, e.g. chat message dispatch.
|
|
||||||
* <p/>
|
|
||||||
* Uses self-type annotation (this: Actor =>) to declare that it needs to be mixed in with an Actor.
|
|
||||||
*/
|
|
||||||
trait ChatManagement { this: Actor =>
|
|
||||||
val sessions: HashMap[String, ActorRef] // needs someone to provide the Session map
|
|
||||||
|
|
||||||
protected def chatManagement: Receive = {
|
|
||||||
case msg @ ChatMessage(from, _) => getSession(from).foreach(_ ! msg)
|
|
||||||
case msg @ GetChatLog(from) => getSession(from).foreach(_ forward msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
private def getSession(from: String) : Option[ActorRef] = {
|
|
||||||
if (sessions.contains(from))
|
|
||||||
Some(sessions(from))
|
|
||||||
else {
|
|
||||||
EventHandler.info(this, "Session expired for %s".format(from))
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates and links a MemoryChatStorage.
|
|
||||||
*/
|
|
||||||
trait MemoryChatStorageFactory { this: Actor =>
|
|
||||||
val storage = actorOf(Props[MemoryChatStorage].withSupervisor(this.self)) // starts and links ChatStorage
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Chat server. Manages sessions and redirects all other messages to the Session for the client.
|
|
||||||
*/
|
|
||||||
trait ChatServer extends Actor {
|
|
||||||
//faultHandler = OneForOneStrategy(List(classOf[Exception]),5, 5000)
|
|
||||||
val storage: ActorRef
|
|
||||||
|
|
||||||
EventHandler.info(this, "Chat server is starting up...")
|
|
||||||
|
|
||||||
// actor message handler
|
|
||||||
def receive: Receive = sessionManagement orElse chatManagement
|
|
||||||
|
|
||||||
// abstract methods to be defined somewhere else
|
|
||||||
protected def chatManagement: Receive
|
|
||||||
protected def sessionManagement: Receive
|
|
||||||
protected def shutdownSessions()
|
|
||||||
|
|
||||||
override def postStop() {
|
|
||||||
EventHandler.info(this, "Chat server is shutting down...")
|
|
||||||
shutdownSessions()
|
|
||||||
storage.stop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class encapsulating the full Chat Service.
|
|
||||||
* Start service by invoking:
|
|
||||||
* <pre>
|
|
||||||
* val chatService = Actor.actorOf[ChatService]
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
class ChatService extends
|
|
||||||
ChatServer with
|
|
||||||
SessionManagement with
|
|
||||||
ChatManagement with
|
|
||||||
MemoryChatStorageFactory {
|
|
||||||
override def preStart() {
|
|
||||||
remote.start("localhost", 2552);
|
|
||||||
remote.register("chat:service", self) //Register the actor with the specified service id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test runner starting ChatService.
|
|
||||||
*/
|
|
||||||
object ServerRunner {
|
|
||||||
|
|
||||||
def main(args: Array[String]) { ServerRunner.run() }
|
|
||||||
|
|
||||||
def run() {
|
|
||||||
actorOf[ChatService]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test runner emulating a chat session.
|
|
||||||
*/
|
|
||||||
object ClientRunner {
|
|
||||||
|
|
||||||
def main(args: Array[String]) { ClientRunner.run() }
|
|
||||||
|
|
||||||
def run() {
|
|
||||||
|
|
||||||
val client1 = new ChatClient("jonas")
|
|
||||||
client1.login
|
|
||||||
val client2 = new ChatClient("patrik")
|
|
||||||
client2.login
|
|
||||||
|
|
||||||
client1.post("Hi there")
|
|
||||||
println("CHAT LOG:\n\t" + client1.chatLog.log.mkString("\n\t"))
|
|
||||||
|
|
||||||
client2.post("Hello")
|
|
||||||
println("CHAT LOG:\n\t" + client2.chatLog.log.mkString("\n\t"))
|
|
||||||
|
|
||||||
client1.post("Hi again")
|
|
||||||
println("CHAT LOG:\n\t" + client1.chatLog.log.mkString("\n\t"))
|
|
||||||
|
|
||||||
client1.logout
|
|
||||||
client2.logout
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
####################
|
|
||||||
# Akka Config File #
|
|
||||||
####################
|
|
||||||
|
|
||||||
akka {
|
|
||||||
version = "2.0-SNAPSHOT"
|
|
||||||
|
|
||||||
enabled-modules = ["http"]
|
|
||||||
|
|
||||||
time-unit = "seconds"
|
|
||||||
|
|
||||||
event-handlers = ["akka.event.EventHandler$DefaultListener"]
|
|
||||||
|
|
||||||
boot = ["sample.hello.Boot"]
|
|
||||||
|
|
||||||
http {
|
|
||||||
hostname = "localhost"
|
|
||||||
port = 9998
|
|
||||||
|
|
||||||
connection-close = true
|
|
||||||
root-actor-id = "_httproot"
|
|
||||||
root-actor-builtin = true
|
|
||||||
timeout = 1000
|
|
||||||
expired-header-name = "Async-Timeout"
|
|
||||||
expired-header-value = "expired"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
<?xml version="1.0"?>
|
|
||||||
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
|
|
||||||
|
|
||||||
<Configure id="Server" class="org.eclipse.jetty.server.Server">
|
|
||||||
|
|
||||||
<!-- =========================================================== -->
|
|
||||||
<!-- Server Thread Pool -->
|
|
||||||
<!-- =========================================================== -->
|
|
||||||
<Set name="ThreadPool">
|
|
||||||
<New class="org.eclipse.jetty.util.thread.ExecutorThreadPool">
|
|
||||||
</New>
|
|
||||||
</Set>
|
|
||||||
|
|
||||||
<!-- =========================================================== -->
|
|
||||||
<!-- Set connectors -->
|
|
||||||
<!-- =========================================================== -->
|
|
||||||
|
|
||||||
<Call name="addConnector">
|
|
||||||
<Arg>
|
|
||||||
<New class="org.eclipse.jetty.server.nio.SelectChannelConnector">
|
|
||||||
<Set name="host"><SystemProperty name="jetty.host" /></Set>
|
|
||||||
<Set name="port"><SystemProperty name="jetty.port" default="8080"/></Set>
|
|
||||||
<Set name="maxIdleTime">300000</Set>
|
|
||||||
<Set name="Acceptors">2</Set>
|
|
||||||
<Set name="statsOn">false</Set>
|
|
||||||
<Set name="confidentialPort">8443</Set>
|
|
||||||
<Set name="lowResourcesConnections">20000</Set>
|
|
||||||
<Set name="lowResourcesMaxIdleTime">5000</Set>
|
|
||||||
</New>
|
|
||||||
</Arg>
|
|
||||||
</Call>
|
|
||||||
|
|
||||||
<!-- =========================================================== -->
|
|
||||||
<!-- Set handler -->
|
|
||||||
<!-- =========================================================== -->
|
|
||||||
<Set name="handler">
|
|
||||||
<New id="Handlers" class="org.eclipse.jetty.server.handler.HandlerCollection">
|
|
||||||
<Set name="handlers">
|
|
||||||
<Array type="org.eclipse.jetty.server.Handler">
|
|
||||||
<Item>
|
|
||||||
<New id="AkkaMistHandler" class="org.eclipse.jetty.servlet.ServletContextHandler">
|
|
||||||
<Set name="contextPath">/</Set>
|
|
||||||
<Call name="addServlet">
|
|
||||||
<Arg>akka.http.AkkaMistServlet</Arg>
|
|
||||||
<Arg>/*</Arg>
|
|
||||||
</Call>
|
|
||||||
</New>
|
|
||||||
</Item>
|
|
||||||
<Item>
|
|
||||||
<New id="DefaultHandler" class="org.eclipse.jetty.server.handler.DefaultHandler"/>
|
|
||||||
</Item>
|
|
||||||
</Array>
|
|
||||||
</Set>
|
|
||||||
</New>
|
|
||||||
</Set>
|
|
||||||
|
|
||||||
<!-- =========================================================== -->
|
|
||||||
<!-- Extra options -->
|
|
||||||
<!-- =========================================================== -->
|
|
||||||
<Set name="stopAtShutdown">true</Set>
|
|
||||||
<Set name="sendServerVersion">true</Set>
|
|
||||||
<Set name="sendDateHeader">true</Set>
|
|
||||||
<Set name="gracefulShutdown">1000</Set>
|
|
||||||
|
|
||||||
</Configure>
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
|
||||||
*/
|
|
||||||
|
|
||||||
package sample.hello
|
|
||||||
|
|
||||||
import akka.actor._
|
|
||||||
import akka.http._
|
|
||||||
|
|
||||||
class Boot {
|
|
||||||
val supervisor = Supervisor(OneForOneStrategy(List(classOf[Exception]), 3, 100))
|
|
||||||
Actor.actorOf(Props[RootEndpoint].withSupervisor(supervisor))
|
|
||||||
Actor.actorOf(Props[HelloEndpoint].withSupervisor(supervisor))
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
|
||||||
*/
|
|
||||||
|
|
||||||
package sample.hello
|
|
||||||
|
|
||||||
import akka.actor._
|
|
||||||
import akka.http._
|
|
||||||
|
|
||||||
import java.text.DateFormat
|
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
class HelloEndpoint extends Actor with Endpoint {
|
|
||||||
self.dispatcher = Endpoint.Dispatcher
|
|
||||||
|
|
||||||
lazy val hello = Actor.actorOf(
|
|
||||||
new Actor {
|
|
||||||
def time = DateFormat.getTimeInstance.format(new Date)
|
|
||||||
def receive = {
|
|
||||||
case get: Get => get OK "Hello at " + time
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
def hook: Endpoint.Hook = { case _ => hello }
|
|
||||||
|
|
||||||
override def preStart = Actor.registry.actorFor(MistSettings.RootActorID).get ! Endpoint.Attach(hook)
|
|
||||||
|
|
||||||
def receive = handleHttpRequest
|
|
||||||
}
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
|
||||||
*/
|
|
||||||
package sample.osgi
|
|
||||||
|
|
||||||
import akka.actor.Actor
|
|
||||||
import akka.actor.Actor._
|
|
||||||
|
|
||||||
import org.osgi.framework.{ BundleActivator, BundleContext }
|
|
||||||
|
|
||||||
class Activator extends BundleActivator {
|
|
||||||
|
|
||||||
def start(context: BundleContext) {
|
|
||||||
println("Starting the OSGi example ...")
|
|
||||||
val echo = actorOf[EchoActor]
|
|
||||||
val answer = (echo ? "OSGi example").as[String]
|
|
||||||
println(answer getOrElse "No answer!")
|
|
||||||
}
|
|
||||||
|
|
||||||
def stop(context: BundleContext) {
|
|
||||||
Actor.registry.local.shutdownAll()
|
|
||||||
println("Stopped the OSGi example.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class EchoActor extends Actor {
|
|
||||||
|
|
||||||
override def receive = {
|
|
||||||
case x => reply(x)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
---------------------------------------------------------
|
|
||||||
== Akka Remote Sample Application ==
|
|
||||||
---------------------------------------------------------
|
|
||||||
= Server Managed Remote Actors Sample =
|
|
||||||
|
|
||||||
To run the sample:
|
|
||||||
|
|
||||||
1. Fire up two shells. For each of them:
|
|
||||||
- Step down into to the root of the Akka distribution.
|
|
||||||
- Set 'export AKKA_HOME=<root of distribution>.
|
|
||||||
- Run 'sbt'
|
|
||||||
- Run 'update' followed by 'compile' if you have not done that before.
|
|
||||||
- Run 'project akka-sample-remote'
|
|
||||||
- Run 'console' to start up a REPL (interpreter).
|
|
||||||
2. In the first REPL you get execute:
|
|
||||||
- scala> import sample.remote._
|
|
||||||
- scala> ServerManagedRemoteActorServer.run
|
|
||||||
This starts up the RemoteNode and registers the remote actor
|
|
||||||
3. In the second REPL you get execute:
|
|
||||||
- scala> import sample.remote._
|
|
||||||
- scala> ServerManagedRemoteActorClient.run
|
|
||||||
4. See the actor conversation.
|
|
||||||
5. Run it again to see full speed after first initialization.
|
|
||||||
|
|
||||||
Now you could test client reconnect by killing the console running the ServerManagedRemoteActorClient and start it up again. See the client reconnect take place in the REPL shell.
|
|
||||||
|
|
||||||
That’s it. Have fun.
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
|
||||||
*/
|
|
||||||
|
|
||||||
package sample.remote
|
|
||||||
|
|
||||||
import akka.actor.Actor._
|
|
||||||
import akka.actor. {ActorRegistry, Actor}
|
|
||||||
|
|
||||||
class HelloWorldActor extends Actor {
|
|
||||||
def receive = {
|
|
||||||
case "Hello" =>
|
|
||||||
reply("World")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object ServerManagedRemoteActorServer {
|
|
||||||
|
|
||||||
def run = {
|
|
||||||
Actor.remote.start("localhost", 2552)
|
|
||||||
Actor.remote.register("hello-service", actorOf[HelloWorldActor])
|
|
||||||
}
|
|
||||||
|
|
||||||
def main(args: Array[String]) = run
|
|
||||||
}
|
|
||||||
|
|
||||||
object ServerManagedRemoteActorClient {
|
|
||||||
|
|
||||||
def run = {
|
|
||||||
val actor = Actor.remote.actorFor("hello-service", "localhost", 2552)
|
|
||||||
val result = actor !! "Hello"
|
|
||||||
}
|
|
||||||
|
|
||||||
def main(args: Array[String]) = run
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,11 +1,18 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
||||||
|
*/
|
||||||
|
|
||||||
package akka
|
package akka
|
||||||
|
|
||||||
import sbt._
|
import sbt._
|
||||||
import Keys._
|
import Keys._
|
||||||
|
|
||||||
import com.typesafe.sbtmultijvm.MultiJvmPlugin
|
import com.typesafe.sbtmultijvm.MultiJvmPlugin
|
||||||
import MultiJvmPlugin.{ MultiJvm, extraOptions, jvmOptions, scalatestOptions }
|
|
||||||
import com.typesafe.sbtscalariform.ScalariformPlugin
|
import com.typesafe.sbtscalariform.ScalariformPlugin
|
||||||
|
|
||||||
|
import MultiJvmPlugin.{ MultiJvm, extraOptions, jvmOptions, scalatestOptions }
|
||||||
import ScalariformPlugin.{ format, formatPreferences, formatSourceDirectories }
|
import ScalariformPlugin.{ format, formatPreferences, formatSourceDirectories }
|
||||||
|
|
||||||
import java.lang.Boolean.getBoolean
|
import java.lang.Boolean.getBoolean
|
||||||
|
|
||||||
object AkkaBuild extends Build {
|
object AkkaBuild extends Build {
|
||||||
|
|
@ -26,7 +33,6 @@ object AkkaBuild extends Build {
|
||||||
rstdocDirectory <<= baseDirectory / "akka-docs"
|
rstdocDirectory <<= baseDirectory / "akka-docs"
|
||||||
),
|
),
|
||||||
aggregate = Seq(actor, testkit, actorTests, stm, remote, slf4j, amqp, mailboxes, akkaSbtPlugin, samples, tutorials, docs)
|
aggregate = Seq(actor, testkit, actorTests, stm, remote, slf4j, amqp, mailboxes, akkaSbtPlugin, samples, tutorials, docs)
|
||||||
//aggregate = Seq(cluster, mailboxes, camel, camelTyped)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
lazy val actor = Project(
|
lazy val actor = Project(
|
||||||
|
|
@ -88,23 +94,6 @@ object AkkaBuild extends Build {
|
||||||
)
|
)
|
||||||
) configs (MultiJvm)
|
) configs (MultiJvm)
|
||||||
|
|
||||||
// lazy val cluster = Project(
|
|
||||||
// id = "akka-cluster",
|
|
||||||
// base = file("akka-cluster"),
|
|
||||||
// dependencies = Seq(stm, actorTests % "test->test", testkit % "test"),
|
|
||||||
// settings = defaultSettings ++ multiJvmSettings ++ Seq(
|
|
||||||
// libraryDependencies ++= Dependencies.cluster,
|
|
||||||
// extraOptions in MultiJvm <<= (sourceDirectory in MultiJvm) { src =>
|
|
||||||
// (name: String) => (src ** (name + ".conf")).get.headOption.map("-Dakka.config=" + _.absolutePath).toSeq
|
|
||||||
// },
|
|
||||||
// scalatestOptions in MultiJvm := Seq("-r", "org.scalatest.akka.QuietReporter"),
|
|
||||||
// jvmOptions in MultiJvm := {
|
|
||||||
// if (getBoolean("sbt.log.noformat")) Seq("-Dakka.test.nocolor=true") else Nil
|
|
||||||
// },
|
|
||||||
// test in Test <<= (test in Test) dependsOn (test in MultiJvm)
|
|
||||||
// )
|
|
||||||
// ) configs (MultiJvm)
|
|
||||||
|
|
||||||
lazy val slf4j = Project(
|
lazy val slf4j = Project(
|
||||||
id = "akka-slf4j",
|
id = "akka-slf4j",
|
||||||
base = file("akka-slf4j"),
|
base = file("akka-slf4j"),
|
||||||
|
|
@ -173,7 +162,7 @@ object AkkaBuild extends Build {
|
||||||
testOptions in Test <+= testRedisMailbox map { test => Tests.Filter(s => test) }
|
testOptions in Test <+= testRedisMailbox map { test => Tests.Filter(s => test) }
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
lazy val zookeeperMailbox = Project(
|
lazy val zookeeperMailbox = Project(
|
||||||
id = "akka-zookeeper-mailbox",
|
id = "akka-zookeeper-mailbox",
|
||||||
base = file("akka-durable-mailboxes/akka-zookeeper-mailbox"),
|
base = file("akka-durable-mailboxes/akka-zookeeper-mailbox"),
|
||||||
|
|
@ -196,23 +185,6 @@ object AkkaBuild extends Build {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
// lazy val camel = Project(
|
|
||||||
// id = "akka-camel",
|
|
||||||
// base = file("akka-camel"),
|
|
||||||
// dependencies = Seq(actor, slf4j, testkit % "test"),
|
|
||||||
// settings = defaultSettings ++ Seq(
|
|
||||||
// libraryDependencies ++= Dependencies.camel
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
|
|
||||||
// can be merged back into akka-camel
|
|
||||||
// lazy val camelTyped = Project(
|
|
||||||
// id = "akka-camel-typed",
|
|
||||||
// base = file("akka-camel-typed"),
|
|
||||||
// dependencies = Seq(camel % "compile;test->test", testkit % "test"),
|
|
||||||
// settings = defaultSettings
|
|
||||||
// )
|
|
||||||
|
|
||||||
// lazy val spring = Project(
|
// lazy val spring = Project(
|
||||||
// id = "akka-spring",
|
// id = "akka-spring",
|
||||||
// base = file("akka-spring"),
|
// base = file("akka-spring"),
|
||||||
|
|
@ -244,23 +216,8 @@ object AkkaBuild extends Build {
|
||||||
base = file("akka-samples"),
|
base = file("akka-samples"),
|
||||||
settings = parentSettings,
|
settings = parentSettings,
|
||||||
aggregate = Seq(fsmSample)
|
aggregate = Seq(fsmSample)
|
||||||
// aggregate = Seq(fsmSample, camelSample)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// lazy val antsSample = Project(
|
|
||||||
// id = "akka-sample-ants",
|
|
||||||
// base = file("akka-samples/akka-sample-ants"),
|
|
||||||
// dependencies = Seq(stm),
|
|
||||||
// settings = defaultSettings
|
|
||||||
// )
|
|
||||||
|
|
||||||
// lazy val chatSample = Project(
|
|
||||||
// id = "akka-sample-chat",
|
|
||||||
// base = file("akka-samples/akka-sample-chat"),
|
|
||||||
// dependencies = Seq(cluster),
|
|
||||||
// settings = defaultSettings
|
|
||||||
// )
|
|
||||||
|
|
||||||
lazy val fsmSample = Project(
|
lazy val fsmSample = Project(
|
||||||
id = "akka-sample-fsm",
|
id = "akka-sample-fsm",
|
||||||
base = file("akka-samples/akka-sample-fsm"),
|
base = file("akka-samples/akka-sample-fsm"),
|
||||||
|
|
@ -268,29 +225,6 @@ object AkkaBuild extends Build {
|
||||||
settings = defaultSettings
|
settings = defaultSettings
|
||||||
)
|
)
|
||||||
|
|
||||||
// lazy val camelSample = Project(
|
|
||||||
// id = "akka-sample-camel",
|
|
||||||
// base = file("akka-samples/akka-sample-camel"),
|
|
||||||
// dependencies = Seq(actor, camelTyped, testkit % "test"),
|
|
||||||
// settings = defaultSettings ++ Seq(
|
|
||||||
// libraryDependencies ++= Dependencies.sampleCamel
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
|
|
||||||
// lazy val helloSample = Project(
|
|
||||||
// id = "akka-sample-hello",
|
|
||||||
// base = file("akka-samples/akka-sample-hello"),
|
|
||||||
// dependencies = Seq(kernel),
|
|
||||||
// settings = defaultSettings
|
|
||||||
// )
|
|
||||||
|
|
||||||
// lazy val remoteSample = Project(
|
|
||||||
// id = "akka-sample-remote",
|
|
||||||
// base = file("akka-samples/akka-sample-remote"),
|
|
||||||
// dependencies = Seq(cluster),
|
|
||||||
// settings = defaultSettings
|
|
||||||
// )
|
|
||||||
|
|
||||||
lazy val tutorials = Project(
|
lazy val tutorials = Project(
|
||||||
id = "akka-tutorials",
|
id = "akka-tutorials",
|
||||||
base = file("akka-tutorials"),
|
base = file("akka-tutorials"),
|
||||||
|
|
@ -424,7 +358,7 @@ object Dependencies {
|
||||||
val amqp = Seq(rabbit, commonsIo, protobuf)
|
val amqp = Seq(rabbit, commonsIo, protobuf)
|
||||||
|
|
||||||
val mailboxes = Seq(Test.scalatest, Test.junit)
|
val mailboxes = Seq(Test.scalatest, Test.junit)
|
||||||
|
|
||||||
val fileMailbox = Seq(Test.scalatest, Test.junit)
|
val fileMailbox = Seq(Test.scalatest, Test.junit)
|
||||||
|
|
||||||
val beanstalkMailbox = Seq(beanstalk, Test.junit)
|
val beanstalkMailbox = Seq(beanstalk, Test.junit)
|
||||||
|
|
@ -432,13 +366,10 @@ object Dependencies {
|
||||||
val redisMailbox = Seq(redis, Test.junit)
|
val redisMailbox = Seq(redis, Test.junit)
|
||||||
|
|
||||||
val mongoMailbox = Seq(mongoAsync, twttrUtilCore, Test.junit)
|
val mongoMailbox = Seq(mongoAsync, twttrUtilCore, Test.junit)
|
||||||
|
|
||||||
val zookeeperMailbox = Seq(zookeeper, Test.junit)
|
val zookeeperMailbox = Seq(zookeeper, Test.junit)
|
||||||
|
|
||||||
// val camel = Seq(camelCore, Test.junit, Test.scalatest, Test.logback)
|
|
||||||
|
|
||||||
val spring = Seq(springBeans, springContext, Test.junit, Test.scalatest)
|
val spring = Seq(springBeans, springContext, Test.junit, Test.scalatest)
|
||||||
// val spring = Seq(springBeans, springContext, camelSpring, Test.junit, Test.scalatest)
|
|
||||||
|
|
||||||
val kernel = Seq(
|
val kernel = Seq(
|
||||||
jettyUtil, jettyXml, jettyServlet, jacksonCore, staxApi
|
jettyUtil, jettyXml, jettyServlet, jacksonCore, staxApi
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue