diff --git a/maven/api/pom.xml b/maven/api/pom.xml
deleted file mode 100644
index ad52103..0000000
--- a/maven/api/pom.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-
-
-
- org.cakesolutions.akkapatterns
- server-parent
- 0.1.RELEASE-SNAPSHOT
- ../parent/pom.xml
-
- 4.0.0
- api
- jar
- Akka Patterns - API
-
-
- org.cakesolutions.akkapatterns
- core
- 0.1.RELEASE-SNAPSHOT
-
-
- org.cakesolutions.akkapatterns
- domain
- 0.1.RELEASE-SNAPSHOT
-
-
- org.cakesolutions.akkapatterns
- test
- 0.1.RELEASE-SNAPSHOT
- test
-
-
-
- cc.spray
- spray-server
-
-
- cc.spray
- spray-util
-
-
- cc.spray
- spray-base
-
-
-
- com.typesafe.akka
- akka-actor_${scala.version}
-
-
-
- net.liftweb
- lift-json_2.9.1
-
-
-
- junit
- junit-dep
- test
-
-
- org.specs2
- specs2_${scala.version}
- test
-
-
-
diff --git a/maven/api/src/main/resources/org/cakesolutions/akkapatterns/api/customers-UUID-get.json b/maven/api/src/main/resources/org/cakesolutions/akkapatterns/api/customers-UUID-get.json
deleted file mode 100644
index c6e54b3..0000000
--- a/maven/api/src/main/resources/org/cakesolutions/akkapatterns/api/customers-UUID-get.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "firstName":"Jan",
- "lastName":"Machacek",
- "email":"janm@cakesolutions.net",
- "id":"00000000-0000-0000-0000-000000000000",
- "addresses":[
- {"line1":"Magdalen Centre", "line2":"Robert Robinson Avenue", "line3":"Oxford"},
- {"line1":"Houldsworth Mill", "line2":"Houldsworth Street", "line3":"Reddish"}
- ]
-}
\ No newline at end of file
diff --git a/maven/api/src/main/resources/org/cakesolutions/akkapatterns/api/customers-get.json b/maven/api/src/main/resources/org/cakesolutions/akkapatterns/api/customers-get.json
deleted file mode 100644
index b9f1d65..0000000
--- a/maven/api/src/main/resources/org/cakesolutions/akkapatterns/api/customers-get.json
+++ /dev/null
@@ -1,10 +0,0 @@
-[{
- "firstName":"Jan",
- "lastName":"Machacek",
- "email":"janm@cakesolutions.net",
- "id":"00000000-0000-0000-0000-000000000000",
- "addresses":[
- {"line1":"Magdalen Centre", "line2":"Robert Robinson Avenue", "line3":"Oxford"},
- {"line1":"Houldsworth Mill", "line2":"Houldsworth Street", "line3":"Reddish"}
- ]
-}]
\ No newline at end of file
diff --git a/maven/api/src/main/resources/org/cakesolutions/akkapatterns/api/customers-post.json b/maven/api/src/main/resources/org/cakesolutions/akkapatterns/api/customers-post.json
deleted file mode 100644
index 945b588..0000000
--- a/maven/api/src/main/resources/org/cakesolutions/akkapatterns/api/customers-post.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "firstName":"Joe",
- "lastName":"Bloggs",
- "email":"joe@cakesolutions.net",
- "id":"00000000-0000-0000-0100-000000000000",
- "addresses":[
- {"line1":"123 Winding Road", "line2":"Cowley", "line3":"Oxford"}
- ]
-}
\ No newline at end of file
diff --git a/maven/api/src/main/scala/org/cakesolutions/akkapatterns/api/boot.scala b/maven/api/src/main/scala/org/cakesolutions/akkapatterns/api/boot.scala
deleted file mode 100644
index baa6a50..0000000
--- a/maven/api/src/main/scala/org/cakesolutions/akkapatterns/api/boot.scala
+++ /dev/null
@@ -1,37 +0,0 @@
-package org.cakesolutions.akkapatterns.api
-
-import akka.actor.{ActorRef, Props}
-import cc.spray._
-import http.{StatusCodes, HttpResponse}
-import org.cakesolutions.akkapatterns.core.Core
-import akka.util.Timeout
-
-trait Api {
- this: Core =>
-
- val routes =
- new HomeService().route ::
- //new DummyService("customers").route ::
- new CustomerService().route ::
- Nil
-
- def rejectionHandler: PartialFunction[scala.List[cc.spray.Rejection], cc.spray.http.HttpResponse] = {
- case (rejections: List[Rejection]) => HttpResponse(StatusCodes.BadRequest)
- }
-
- val svc: Route => ActorRef = route => actorSystem.actorOf(Props(new HttpService(route, rejectionHandler)))
-
- val rootService = actorSystem.actorOf(
- props = Props(new RootService(
- svc(routes.head),
- routes.tail.map(svc):_*
- )),
- name = "root-service"
- )
-
-}
-
-trait DefaultTimeout {
- final implicit val timeout = Timeout(3000)
-
-}
\ No newline at end of file
diff --git a/maven/api/src/main/scala/org/cakesolutions/akkapatterns/api/customer.scala b/maven/api/src/main/scala/org/cakesolutions/akkapatterns/api/customer.scala
deleted file mode 100644
index 0657d52..0000000
--- a/maven/api/src/main/scala/org/cakesolutions/akkapatterns/api/customer.scala
+++ /dev/null
@@ -1,37 +0,0 @@
-package org.cakesolutions.akkapatterns.api
-
-import akka.actor.ActorSystem
-import cc.spray.Directives
-import org.cakesolutions.akkapatterns.domain.Customer
-import org.cakesolutions.akkapatterns.core.application._
-import cc.spray.directives.JavaUUID
-import akka.pattern.ask
-import org.cakesolutions.akkapatterns.core.application.RegisterCustomer
-import org.cakesolutions.akkapatterns.domain.Customer
-import org.cakesolutions.akkapatterns.core.application.Get
-import org.cakesolutions.akkapatterns.core.application.FindAll
-
-/**
- * @author janmachacek
- */
-class CustomerService(implicit val actorSystem: ActorSystem) extends Directives with Marshallers with Unmarshallers with DefaultTimeout with LiftJSON {
- def customerActor = actorSystem.actorFor("/user/application/customer")
-
- val route =
- path("customers" / JavaUUID) { id =>
- get {
- completeWith((customerActor ? Get(id)).mapTo[Option[Customer]])
- }
- } ~
- path("customers") {
- get {
- completeWith((customerActor ? FindAll()).mapTo[List[Customer]])
- } ~
- post {
- content(as[RegisterCustomer]) { rc =>
- completeWith((customerActor ? rc).mapTo[Either[NotRegisteredCustomer, RegisteredCustomer]])
- }
- }
- }
-
-}
diff --git a/maven/api/src/main/scala/org/cakesolutions/akkapatterns/api/dummy.scala b/maven/api/src/main/scala/org/cakesolutions/akkapatterns/api/dummy.scala
deleted file mode 100644
index 1c792b4..0000000
--- a/maven/api/src/main/scala/org/cakesolutions/akkapatterns/api/dummy.scala
+++ /dev/null
@@ -1,47 +0,0 @@
-package org.cakesolutions.akkapatterns.api
-
-import cc.spray.Directives
-import akka.actor.ActorSystem
-import cc.spray.http._
-import cc.spray.http.MediaTypes._
-import cc.spray.RequestContext
-
-class DummyService(path: String)(implicit val actorSystem: ActorSystem) extends Directives {
-
- val route = {
- pathPrefix(path) {
- x =>
- x.complete(
- HttpResponse(StatusCodes.OK, getContent(x, `application/json`)))
- }
- }
-
- private def getContent(ctx: RequestContext, contentType: ContentType): HttpContent = {
-
- var filename = ctx.request.path
-
- if (filename.startsWith("/")) filename = filename.drop(1)
- if (filename.endsWith("/")) filename = filename.dropRight(1)
- filename = filename.replace("/", "-")
- filename = filename + "-" + ctx.request.method.toString().toLowerCase + ".json"
-
- val uidRegex = "[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}".r
- val fileContent = uidRegex.findFirstIn(filename) match {
- case Some(uid) =>
- getFileAsString(uidRegex.replaceAllIn(filename, "UUID"))
- case None =>
- getFileAsString(filename)
- }
-
- HttpContent(contentType, fileContent)
- }
-
- private def getFileAsString(filename: String): String = {
- try {
- scala.io.Source.fromInputStream(getClass.getResourceAsStream(filename)).mkString
- }
- catch {
- case _: Throwable => "{body of file " + filename + " -- Missing File!}"
- }
- }
-}
diff --git a/maven/api/src/main/scala/org/cakesolutions/akkapatterns/api/home.scala b/maven/api/src/main/scala/org/cakesolutions/akkapatterns/api/home.scala
deleted file mode 100644
index c701411..0000000
--- a/maven/api/src/main/scala/org/cakesolutions/akkapatterns/api/home.scala
+++ /dev/null
@@ -1,38 +0,0 @@
-package org.cakesolutions.akkapatterns.api
-
-import akka.actor.ActorSystem
-import cc.spray.Directives
-import cc.spray.directives.Slash
-import java.net.InetAddress
-import akka.pattern.ask
-import org.cakesolutions.akkapatterns.core.application.{PoisonPill, GetImplementation, Implementation}
-
-case class SystemInfo(implementation: Implementation, host: String)
-
-class HomeService(implicit val actorSystem: ActorSystem) extends Directives with Marshallers with DefaultTimeout with LiftJSON {
-
- def applicationActor = actorSystem.actorFor("/user/application")
- import scala.concurrent.ExecutionContext.Implicits.global
-
- val route = {
- path(Slash) {
- get {
- completeWith {
- (applicationActor ? GetImplementation()).mapTo[Implementation].map {
- SystemInfo(_, InetAddress.getLocalHost.getCanonicalHostName)
- }
- }
- }
- } ~
- path("poisonpill") {
- post {
- completeWith {
- applicationActor ! PoisonPill()
-
- "Goodbye"
- }
- }
- }
- }
-
-}
diff --git a/maven/api/src/main/scala/org/cakesolutions/akkapatterns/api/marshalling.scala b/maven/api/src/main/scala/org/cakesolutions/akkapatterns/api/marshalling.scala
deleted file mode 100644
index c63a351..0000000
--- a/maven/api/src/main/scala/org/cakesolutions/akkapatterns/api/marshalling.scala
+++ /dev/null
@@ -1,68 +0,0 @@
-package org.cakesolutions.akkapatterns.api
-
-import cc.spray.typeconversion._
-import net.liftweb.json._
-import cc.spray.http.{HttpContent, ContentType}
-import cc.spray.http.MediaTypes._
-import net.liftweb.json.Serialization._
-import cc.spray.http.ContentTypeRange
-import java.util.UUID
-
-trait Marshallers extends DefaultMarshallers {
- this: LiftJSON =>
-
- implicit def liftJsonMarshaller[A <: AnyRef] = new SimpleMarshaller[A] {
- val canMarshalTo = ContentType(`application/json`) :: Nil
- def marshal(value: A, contentType: ContentType) = {
- val jsonSource = write(value.asInstanceOf[AnyRef])
- DefaultMarshallers.StringMarshaller.marshal(jsonSource, contentType)
- }
- }
-
-}
-
-trait Unmarshallers extends DefaultUnmarshallers {
- this: LiftJSON =>
-
- implicit def liftJsonUnmarshaller[A <: Product : Manifest] = new SimpleUnmarshaller[A] {
- val canUnmarshalFrom = ContentTypeRange(`application/json`) :: Nil
- def unmarshal(content: HttpContent) = protect {
- val jsonSource = DefaultUnmarshallers.StringUnmarshaller(content).right.get
- parse(jsonSource).extract[A]
- }
- }
-
-}
-
-trait LiftJSON {
- implicit def liftJsonFormats: Formats =
- DefaultFormats + new UUIDSerializer + FieldSerializer[AnyRef]()
-
- class UUIDSerializer extends Serializer[UUID] {
- private val UUIDClass = classOf[UUID]
-
- def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), UUID] = {
- case (TypeInfo(UUIDClass, _), json) => json match {
- case JString(s) => UUID.fromString(s)
- case x => throw new MappingException("Can't convert " + x + " to UUID")
- }
- }
-
- def serialize(implicit format: Formats): PartialFunction[Any, JValue] = {
- case x: UUID => JString(x.toString)
- }
- }
-
- class StringBuilderMarshallingContent(sb: StringBuilder) extends MarshallingContext {
-
- def marshalTo(content: HttpContent) {
- if (sb.length > 0) sb.append(",")
- sb.append(new String(content.buffer))
- }
-
- def handleError(error: Throwable) {}
-
- def startChunkedMessage(contentType: ContentType) = throw new UnsupportedOperationException
- }
-
-}
\ No newline at end of file
diff --git a/maven/api/src/main/scala/org/cakesolutions/akkapatterns/api/user.scala b/maven/api/src/main/scala/org/cakesolutions/akkapatterns/api/user.scala
deleted file mode 100644
index da6b990..0000000
--- a/maven/api/src/main/scala/org/cakesolutions/akkapatterns/api/user.scala
+++ /dev/null
@@ -1,24 +0,0 @@
-package org.cakesolutions.akkapatterns.api
-
-import akka.actor.ActorSystem
-import cc.spray.Directives
-import org.cakesolutions.akkapatterns.domain.User
-import org.cakesolutions.akkapatterns.core.application.{NotRegisteredUser, RegisteredUser}
-import akka.pattern.ask
-
-/**
- * @author janmachacek
- */
-class UserService(implicit val actorSystem: ActorSystem) extends Directives with Marshallers with Unmarshallers with DefaultTimeout with LiftJSON {
- def userActor = actorSystem.actorFor("/user/application/user")
-
- val route =
- path("user" / "register") {
- post {
- content(as[User]) { user =>
- completeWith((userActor ? RegisteredUser(user)).mapTo[Either[NotRegisteredUser, RegisteredUser]])
- }
- }
- }
-
-}
diff --git a/maven/api/src/test/scala/org/cakesolutions/akkapatterns/api/CustomerServiceSpec.scala b/maven/api/src/test/scala/org/cakesolutions/akkapatterns/api/CustomerServiceSpec.scala
deleted file mode 100644
index 9ce15ce..0000000
--- a/maven/api/src/test/scala/org/cakesolutions/akkapatterns/api/CustomerServiceSpec.scala
+++ /dev/null
@@ -1,37 +0,0 @@
-package org.cakesolutions.akkapatterns.api
-
-import org.cakesolutions.akkapatterns.domain.{User, Customer}
-import cc.spray.http.HttpMethods._
-import org.cakesolutions.akkapatterns.core.application.{RegisteredCustomer, RegisterCustomer}
-import java.util.UUID
-
-/**
- * @author janmachacek
- */
-class CustomerServiceSpec extends DefaultApiSpecification {
- implicit val service = rootService
-
- "Getting a known customer works" in {
- val customer = perform[Customer](GET, "/customers/00000000-0000-0000-0000-000000000000")
-
- customer must_== janMachacek
- }
-
- "Finding all customers works" in {
- val customers = perform[List[Customer]](GET, "/customers")
-
- customers must contain (janMachacek)
- }
-
- "Registering a customer" in {
- val rc = RegisterCustomer(
- joeBloggs,
- User(UUID.randomUUID(), "janm", "Like I'll tell you!"))
- val registered = perform[RegisterCustomer, RegisteredCustomer](POST, "/customers", rc)
-
- (registered.customer must_== joeBloggs) and
- (registered.user.username must_== "janm") and
- (registered.user.password must_!= "Like I'll tell you")
- }
-
-}
diff --git a/maven/api/src/test/scala/org/cakesolutions/akkapatterns/api/HomeServiceSpec.scala b/maven/api/src/test/scala/org/cakesolutions/akkapatterns/api/HomeServiceSpec.scala
deleted file mode 100644
index 6d7a644..0000000
--- a/maven/api/src/test/scala/org/cakesolutions/akkapatterns/api/HomeServiceSpec.scala
+++ /dev/null
@@ -1,18 +0,0 @@
-package org.cakesolutions.akkapatterns.api
-
-import cc.spray.http.HttpMethods._
-import cc.spray.http._
-import org.specs2.runner.JUnitRunner
-import org.junit.runner.RunWith
-
-@RunWith(classOf[JUnitRunner])
-class HomeServiceSpec extends DefaultApiSpecification {
-
- "root URL shows the System version" in {
- testRoot(HttpRequest(GET, "/"))(rootService).response.content.as[SystemInfo] match {
- case Right(info) => success
- case Left(failure) => anError
- }
- }
-
-}
diff --git a/maven/api/src/test/scala/org/cakesolutions/akkapatterns/api/support.scala b/maven/api/src/test/scala/org/cakesolutions/akkapatterns/api/support.scala
deleted file mode 100644
index f241744..0000000
--- a/maven/api/src/test/scala/org/cakesolutions/akkapatterns/api/support.scala
+++ /dev/null
@@ -1,83 +0,0 @@
-package org.cakesolutions.akkapatterns.api
-
-import cc.spray.test.SprayTest
-import java.util.concurrent.TimeUnit
-import akka.actor.ActorRef
-import cc.spray.RequestContext
-import cc.spray.http._
-import io.Source
-import org.specs2.mutable.Specification
-import org.cakesolutions.akkapatterns.test.{DefaultTestData, SpecConfiguration}
-import org.cakesolutions.akkapatterns.core.Core
-import concurrent.util.Duration
-
-trait RootSprayTest extends SprayTest {
- protected def testRoot(request: HttpRequest, timeout: Duration = Duration(10000, TimeUnit.MILLISECONDS))
- (root: ActorRef): ServiceResultWrapper = {
- val routeResult = new RouteResult
- root !
- RequestContext(
- request = request,
- responder = routeResult.requestResponder,
- unmatchedPath = request.path
- )
-
- // since the route might detach we block until the route actually completes or times out
- routeResult.awaitResult(timeout)
- new ServiceResultWrapper(routeResult, timeout)
- }
-
-}
-
-trait JsonSource {
-
- def jsonFor(location: String) = Source.fromInputStream(classOf[JsonSource].getResourceAsStream(location)).mkString
-
- def jsonContent(location: String) = Some(HttpContent(ContentType(MediaTypes.`application/json`), jsonFor(location)))
-
-}
-
-/**
- * Convenience trait for API tests
- */
-trait ApiSpecification extends Specification with SpecConfiguration with RootSprayTest with Core with Api with Unmarshallers with Marshallers with LiftJSON {
-
- import cc.spray.typeconversion._
-
- protected def respond(method: HttpMethod, url: String, content: Option[HttpContent] = None)
- (implicit root: ActorRef) = {
- val request = HttpRequest(method, url, content = content)
- testRoot(request)(root).response
- }
-
- protected def perform[A](method: HttpMethod, url: String, content: Option[HttpContent] = None)
- (implicit root: ActorRef, unmarshaller: Unmarshaller[A]): A = {
- val request = HttpRequest(method, url, content = content)
- val response = testRoot(request)(root).response.content
- val obj = response.as[A] match {
- case Left(e) => throw new Exception(e.toString)
- case Right(r) => r
- }
- obj
- }
-
- protected def perform[In, Out](method: HttpMethod, url: String, in: In)
- (implicit root: ActorRef, marshaller: Marshaller[In], unmarshaller: Unmarshaller[Out]): Out = {
- marshaller(t => Some(t)) match {
- case MarshalWith(f) =>
- val sb = new StringBuilder()
- val ctx = new StringBuilderMarshallingContent(sb)
- f(ctx)(in)
-
- perform[Out](method, url, Some(HttpContent(ContentType(MediaTypes.`application/json`), sb.toString())))
- case CantMarshal(_) =>
- throw new Exception("Cant marshal " + in)
- }
- }
-
-}
-
-/**
- * Convenience trait for API tests; with default test data
- */
-trait DefaultApiSpecification extends ApiSpecification with DefaultTestData with JsonSource
\ No newline at end of file
diff --git a/maven/core/pom.xml b/maven/core/pom.xml
deleted file mode 100644
index fb31fa7..0000000
--- a/maven/core/pom.xml
+++ /dev/null
@@ -1,72 +0,0 @@
-
-
-
- org.cakesolutions.akkapatterns
- server-parent
- 0.1.RELEASE-SNAPSHOT
- ../parent/pom.xml
-
- 4.0.0
- core
- jar
- Akka Patterns - Core
-
-
- com.typesafe.akka
- akka-actor_${scala.version}
-
-
- org.cakesolutions.akkapatterns
- domain
- 0.1.RELEASE-SNAPSHOT
-
-
-
-
-
- org.scalaz
- scalaz-core_${scala.version}
-
-
-
- com.typesafe.akka
- akka-testkit_${scala.version}
- test
-
-
- org.specs2
- specs2_${scala.version}
- test
-
-
- org.specs2
- specs2-scalaz-core_${scala.version}
- test
-
-
- junit
- junit
- 4.8.2
- test
-
-
- org.cakesolutions.akkapatterns
- test
- 0.1.RELEASE-SNAPSHOT
-
-
-
diff --git a/maven/core/src/main/resources/META-INF/aop.xml b/maven/core/src/main/resources/META-INF/aop.xml
deleted file mode 100644
index a8655ab..0000000
--- a/maven/core/src/main/resources/META-INF/aop.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
diff --git a/maven/core/src/main/resources/META-INF/spring/module-context.xml b/maven/core/src/main/resources/META-INF/spring/module-context.xml
deleted file mode 100644
index 72fb8a6..0000000
--- a/maven/core/src/main/resources/META-INF/spring/module-context.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
diff --git a/maven/core/src/main/resources/application.conf b/maven/core/src/main/resources/application.conf
deleted file mode 100644
index 7a5734f..0000000
--- a/maven/core/src/main/resources/application.conf
+++ /dev/null
@@ -1,40 +0,0 @@
-akka {
- loglevel = DEBUG
-
- actor {
- debug {
- event-stream = on
- receive = on
- lifecycle = on
- }
-
- default-dispatcher {
- type = "Dispatcher"
-
- executor = "fork-join-executor"
-
- fork-join-executor {
- # Min number of threads to cap factor-based parallelism number to
- parallelism-min = 8
- # Parallelism (threads) ... ceil(available processors * factor)
- parallelism-factor = 15.0
- # Max number of threads to cap factor-based parallelism number to
- parallelism-max = 64
- }
-
- throughput = 5
-
- attempt-teamwork = on
-
- }
- }
-}
-
-spray {
- can.server {
- idle-timeout = 5 s
- request-timeout = 2 s
- }
-
- io.confirm-sends = off
-}
\ No newline at end of file
diff --git a/maven/core/src/main/scala/org/cakesolutions/akkapatterns/core/application/application.scala b/maven/core/src/main/scala/org/cakesolutions/akkapatterns/core/application/application.scala
deleted file mode 100644
index d7d0d2a..0000000
--- a/maven/core/src/main/scala/org/cakesolutions/akkapatterns/core/application/application.scala
+++ /dev/null
@@ -1,48 +0,0 @@
-package org.cakesolutions.akkapatterns.core.application
-
-import org.cakesolutions.akkapatterns.core.{Started, Stop, Start}
-import akka.actor.{Props, Actor}
-import org.cakesolutions.akkapatterns.domain.Configured
-import com.mongodb.casbah.MongoDB
-
-case class GetImplementation()
-case class Implementation(title: String, version: String, build: String)
-
-case class PoisonPill()
-
-class ApplicationActor extends Actor {
-
- def receive = {
- case GetImplementation() =>
- val manifestStream = getClass.getResourceAsStream("/META-INF/MANIFEST.MF")
- val manifest = new java.util.jar.Manifest(manifestStream)
- val title = manifest.getMainAttributes.getValue("Implementation-Title")
- val version = manifest.getMainAttributes.getValue("Implementation-Version")
- val build = manifest.getMainAttributes.getValue("Implementation-Build")
- manifestStream.close()
-
- sender ! Implementation(title, version, build)
-
- case Start() =>
- context.actorOf(Props[CustomerActor], "customer")
- context.actorOf(Props[UserActor], "user")
-
- sender ! Started()
-
- /*
- * Stops this actor and all the child actors.
- */
- case Stop() =>
- context.children.foreach(context.stop _)
-
- case PoisonPill() =>
- sys.exit(-1)
- }
-
-}
-
-trait MongoCollections extends Configured {
- def customers = configured[MongoDB].apply("customers")
- def users = configured[MongoDB].apply("users")
-
-}
diff --git a/maven/core/src/main/scala/org/cakesolutions/akkapatterns/core/application/casbah.scala b/maven/core/src/main/scala/org/cakesolutions/akkapatterns/core/application/casbah.scala
deleted file mode 100644
index 9a04de1..0000000
--- a/maven/core/src/main/scala/org/cakesolutions/akkapatterns/core/application/casbah.scala
+++ /dev/null
@@ -1,125 +0,0 @@
-package org.cakesolutions.akkapatterns.core.application
-
-import com.mongodb.casbah.Imports._
-import java.util.UUID
-import org.cakesolutions.akkapatterns.domain.{User, Address, Customer}
-
-/**
- * Contains type classes that deserialize records from Casbah into "our" types.
- */
-trait CasbahDeserializers {
- type CasbahDeserializer[A] = DBObject => A
-
- /**
- * Convenience method that picks the ``CasbahDeserializer`` for the type ``A``
- * @param deserializer implicitly given deserializer
- * @tparam A the type A
- * @return the deserializer for ``A``
- */
- def casbahDeserializer[A](implicit deserializer: CasbahDeserializer[A]) = deserializer
-
- private def inner[A: CasbahDeserializer](o: DBObject, field: String): A = casbahDeserializer[A].apply(o.as[DBObject](field))
-
- private def innerList[A: CasbahDeserializer](o: DBObject, field: String): Seq[A] = {
- val deserializer = casbahDeserializer[A]
- o.as[MongoDBList](field).map {
- inner => deserializer(inner.asInstanceOf[DBObject])
- }
- }
-
- implicit object AddressDeserializer extends CasbahDeserializer[Address] {
- def apply(o: DBObject) =
- Address(o.as[String]("line1"), o.as[String]("line2"), o.as[String]("line3"))
- }
-
- implicit object CustomerDeserializer extends CasbahDeserializer[Customer] {
- def apply(o: DBObject) =
- Customer(o.as[String]("firstName"), o.as[String]("lastName"), o.as[String]("email"),
- innerList[Address](o, "addresses"), o.as[UUID]("id"))
- }
-
- implicit object UserDeserializer extends CasbahDeserializer[User] {
- def apply(o: DBObject) = User(o.as[UUID]("id"), o.as[String]("username"), o.as[String]("password"))
- }
-
-}
-
-/**
- * Contains type classes that serialize "our" types into Casbah records.
- */
-trait CasbahSerializers {
- type CasbahSerializer[A] = A => DBObject
-
- /**
- * Convenience method that picks the ``CasbahSerializer`` for the type ``A``
- * @param serializer implicitly given serializer
- * @tparam A the type A
- * @return the serializer for ``A``
- */
- def casbahSerializer[A](implicit serializer: CasbahSerializer[A]) = serializer
-
- implicit object AddressSerializer extends CasbahSerializer[Address] {
- def apply(address: Address) = {
- val builder = MongoDBObject.newBuilder
-
- builder += "line1" -> address.line1
- builder += "line2" -> address.line2
- builder += "line3" -> address.line2
-
- builder.result()
- }
- }
-
- implicit object UserSerializer extends CasbahSerializer[User] {
- def apply(user: User) = {
- val builder = MongoDBObject.newBuilder
-
- builder += "username" -> user.username
- builder += "password" -> user.password
- builder += "id" -> user.id
-
- builder.result()
- }
- }
-
- implicit object CustomerSerializer extends CasbahSerializer[Customer] {
- def apply(customer: Customer) = {
- val builder = MongoDBObject.newBuilder
-
- builder += "firstName" -> customer.firstName
- builder += "lastName" -> customer.lastName
- builder += "email" -> customer.email
- builder += "addresses" -> customer.addresses.map(AddressSerializer(_))
- builder += "id" -> customer.id
-
- builder.result()
- }
- }
-
-}
-
-/**
- * Contains convenience functions that can be used to find "entities-by-id"
- */
-trait SearchExpressions {
-
- def entityId(id: UUID) = MongoDBObject("id" -> id)
-
-// def entityId(id: UUID) = MongoDBObject("id" -> id, "active" -> true)
-
-}
-
-/**
- * Mix this trait into your classes to gain the functionality of the serializers, deserializers and mappers.
- */
-trait TypedCasbah extends CasbahDeserializers with CasbahSerializers {
-
- final def serialize[A: CasbahSerializer](a: A) = casbahSerializer[A].apply(a)
-
- final def deserialize[A: CasbahDeserializer](o: DBObject) = casbahDeserializer[A].apply(o)
-
- final def mapper[A: CasbahDeserializer] = {
- (o: DBObject) => deserialize[A](o)
- }
-
-}
\ No newline at end of file
diff --git a/maven/core/src/main/scala/org/cakesolutions/akkapatterns/core/application/customer.scala b/maven/core/src/main/scala/org/cakesolutions/akkapatterns/core/application/customer.scala
deleted file mode 100644
index d6e9519..0000000
--- a/maven/core/src/main/scala/org/cakesolutions/akkapatterns/core/application/customer.scala
+++ /dev/null
@@ -1,72 +0,0 @@
-package org.cakesolutions.akkapatterns.core.application
-
-import akka.actor.Actor
-import java.util.UUID
-import org.cakesolutions.akkapatterns.domain.{User, Configured, Customer}
-import com.mongodb.casbah.{MongoCollection, MongoDB}
-import org.specs2.internal.scalaz.Identity
-import org.cakesolutions.akkapatterns.domain
-
-/**
- * Registers a customer and a user. After registering, we have a user account for the given customer.
- *
- * @param customer the customer
- * @param user the user
- */
-case class RegisterCustomer(customer: Customer, user: User)
-
-/**
- * Reply to successful customer registration
- * @param customer the newly registered customer
- * @param user the newly registered user
- */
-case class RegisteredCustomer(customer: Customer, user: User)
-
-/**
- * Reply to unsuccessful customer registration
- * @param code the error code for the failure reason
- */
-case class NotRegisteredCustomer(code: String) extends Failure
-
-/**
- * CRUD operations for the [[org.cakesolutions.akkapatterns.domain.Customer]]s
- */
-trait CustomerOperations extends TypedCasbah with SearchExpressions {
- def customers: MongoCollection
-
- def getCustomer(id: domain.Identity) = customers.findOne(entityId(id)).map(mapper[Customer])
-
- def findAllCustomers() = customers.find().map(mapper[Customer]).toList
-
- def insertCustomer(customer: Customer) = {
- customers += serialize(customer)
- customer
- }
-
- def registerCustomer(customer: Customer)(ru: RegisteredUser): Either[Failure, RegisteredCustomer] = {
- customers += serialize(customer)
- Right(RegisteredCustomer(customer, ru.user))
- }
-
-}
-
-class CustomerActor extends Actor with Configured with CustomerOperations with UserOperations with MongoCollections {
-
- protected def receive = {
- case Get(id) =>
- sender ! getCustomer(id)
-
- case FindAll() =>
- sender ! findAllCustomers()
-
- case Insert(customer: Customer) =>
- sender ! insertCustomer(customer)
-
- case RegisterCustomer(customer, user) =>
- import scalaz._
- import Scalaz._
-
- sender ! (registerUser(user) >>= registerCustomer(customer))
-
- }
-}
diff --git a/maven/core/src/main/scala/org/cakesolutions/akkapatterns/core/application/messages.scala b/maven/core/src/main/scala/org/cakesolutions/akkapatterns/core/application/messages.scala
deleted file mode 100644
index 6e4b315..0000000
--- a/maven/core/src/main/scala/org/cakesolutions/akkapatterns/core/application/messages.scala
+++ /dev/null
@@ -1,42 +0,0 @@
-package org.cakesolutions.akkapatterns.core.application
-
-import java.util.UUID
-
-/**
- * Base type for failures
- */
-trait Failure {
- /**
- * The error code for the failure
- * @return the error code
- */
- def code: String
-}
-
-/**
- * Gets an entity identified by ``id``
- *
- * @param id the identity
- */
-case class Get(id: UUID)
-
-/**
- * Finds all entities
- */
-case class FindAll()
-
-/**
- * Inserts the given entity
- *
- * @param entity the entity to be inserted
- * @tparam A the type of A
- */
-case class Insert[A](entity: A)
-
-/**
- * Updates the given entity
- *
- * @param entity the entity to update
- * @tparam A the type of A
- */
-case class Update[A](entity: A)
\ No newline at end of file
diff --git a/maven/core/src/main/scala/org/cakesolutions/akkapatterns/core/application/user.scala b/maven/core/src/main/scala/org/cakesolutions/akkapatterns/core/application/user.scala
deleted file mode 100644
index 0a32179..0000000
--- a/maven/core/src/main/scala/org/cakesolutions/akkapatterns/core/application/user.scala
+++ /dev/null
@@ -1,72 +0,0 @@
-package org.cakesolutions.akkapatterns.core.application
-
-import akka.actor.Actor
-import com.mongodb.casbah.MongoCollection
-import org.cakesolutions.akkapatterns.domain
-import domain.User
-import com.mongodb.casbah.commons.MongoDBObject
-import java.security.MessageDigest
-
-/**
- * Finds a user by the given username
- *
- * @param username the username
- */
-case class GetUserByUsername(username: String)
-
-/**
- * Registers a user. Checks the password complexity and that the username is not duplicate
- *
- * @param user the user to be registered
- */
-case class Register(user: User)
-
-/**
- * Successfully registered a user
- *
- * @param user the user that's just been registered
- */
-case class RegisteredUser(user: User)
-
-/**
- * Unsuccessful registration with the error code
- * @param code the error code
- */
-case class NotRegisteredUser(code: String) extends Failure
-
-
-trait UserOperations extends TypedCasbah with SearchExpressions {
- def users: MongoCollection
- def sha1 = MessageDigest.getInstance("SHA1")
-
- def getUser(id: domain.Identity) = users.findOne(entityId(id)).map(mapper[User])
-
- def getUserByUsername(username: String) = users.findOne(MongoDBObject("username" -> username)).map(mapper[User])
-
- def registerUser(user: User): Either[Failure, RegisteredUser] = {
- getUserByUsername(user.username) match {
- case None =>
- val hashedPassword = java.util.Arrays.toString(sha1.digest(user.password.getBytes))
- val userToRegister = user.copy(password = hashedPassword)
- users += serialize(userToRegister)
- Right(RegisteredUser(userToRegister))
- case Some(_existingUser) =>
- Left(NotRegisteredUser("User.duplicateUsername"))
- }
- }
-
-}
-
-class UserActor extends Actor with UserOperations with MongoCollections {
-
- protected def receive = {
- case Get(id) =>
- sender ! getUser(id)
-
- case GetUserByUsername(username) =>
- sender ! getUserByUsername(username)
-
- case RegisteredUser(user) =>
- sender ! registerUser(user)
- }
-}
diff --git a/maven/core/src/main/scala/org/cakesolutions/akkapatterns/core/boot.scala b/maven/core/src/main/scala/org/cakesolutions/akkapatterns/core/boot.scala
deleted file mode 100644
index 7074074..0000000
--- a/maven/core/src/main/scala/org/cakesolutions/akkapatterns/core/boot.scala
+++ /dev/null
@@ -1,25 +0,0 @@
-package org.cakesolutions.akkapatterns.core
-
-import akka.actor.{Props, ActorSystem}
-import application.ApplicationActor
-import akka.pattern.ask
-import akka.util.Timeout
-import akka.dispatch.Await
-
-case class Start()
-case class Started()
-
-case class Stop()
-
-trait Core {
- implicit def actorSystem: ActorSystem
- implicit val timeout = Timeout(30000)
-
- val application = actorSystem.actorOf(
- props = Props[ApplicationActor],
- name = "application"
- )
-
- Await.ready(application ? Start(), timeout.duration)
-
-}
diff --git a/maven/core/src/test/scala/org/cakesolutions/akkapatterns/core/CustomerActorSpecx.scala b/maven/core/src/test/scala/org/cakesolutions/akkapatterns/core/CustomerActorSpecx.scala
deleted file mode 100644
index 91625c1..0000000
--- a/maven/core/src/test/scala/org/cakesolutions/akkapatterns/core/CustomerActorSpecx.scala
+++ /dev/null
@@ -1,46 +0,0 @@
-package org.cakesolutions.akkapatterns.core
-
-import application.{Insert, FindAll, Get, CustomerActor}
-import org.cakesolutions.akkapatterns.test.{SpecConfiguration, DefaultTestData}
-import org.specs2.mutable.Specification
-import akka.testkit.{ImplicitSender, TestActorRef, TestKit}
-import akka.actor.ActorSystem
-import org.cakesolutions.akkapatterns.domain.Customer
-import org.specs2.runner.JUnitRunner
-import org.junit.runner.RunWith
-
-/**
- * @author janmachacek
- */
-@RunWith(classOf[JUnitRunner])
-class CustomerActorSpecx extends TestKit(ActorSystem()) with Specification with SpecConfiguration with DefaultTestData with ImplicitSender {
- val customerActor = TestActorRef[CustomerActor]
-
- "Getting a known customer works" in {
- customerActor ! Get(janMachacek.id)
- expectMsg(Some(janMachacek))
-
- success
- }
-
- "Finding all customers includes jan" in {
- customerActor ! FindAll()
- val customers = expectMsgType[List[Customer]]
-
- customers must contain (janMachacek)
- }
-
- "Inserting a new customer must then find it" in {
- customerActor ! Get(joeBloggs.id)
- expectMsg(None)
-
- customerActor ! Insert(joeBloggs)
- val insertedJoeBloggs = expectMsgType[Customer]
-
- customerActor ! Get(insertedJoeBloggs.id)
- val loadedJoeBloggs = expectMsgType[Option[Customer]].get
-
- loadedJoeBloggs.firstName must_== insertedJoeBloggs.firstName
- }
-
-}
diff --git a/maven/domain/pom.xml b/maven/domain/pom.xml
deleted file mode 100644
index fe0375f..0000000
--- a/maven/domain/pom.xml
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-
- org.cakesolutions.akkapatterns
- server-parent
- 0.1.RELEASE-SNAPSHOT
- ../parent/pom.xml
-
- 4.0.0
- domain
- jar
- Akka Patterns - Domain
-
-
- org.specs2
- specs2_${scala.version}
-
-
-
-
-
-
diff --git a/maven/domain/src/main/scala/org/cakesolutions/akkapatterns/domain/configuration.scala b/maven/domain/src/main/scala/org/cakesolutions/akkapatterns/domain/configuration.scala
deleted file mode 100644
index 4a186aa..0000000
--- a/maven/domain/src/main/scala/org/cakesolutions/akkapatterns/domain/configuration.scala
+++ /dev/null
@@ -1,43 +0,0 @@
-package org.cakesolutions.akkapatterns.domain
-
-import collection.mutable
-
-private object ConfigurationStore {
- val entries = mutable.Map[String, AnyRef]()
-
- def put(key: String, value: AnyRef) {
- entries += ((key, value))
- }
-
- def get[A: Manifest] = {
- val erasure = manifest[A].erasure
- entries.values.find(x => erasure.isAssignableFrom(x.getClass)) match {
- case Some(v) => Some(v.asInstanceOf[A])
- case None => None
- }
- }
-}
-
-trait Configured {
-
- def configured[A: Manifest, U](f: A => U) = f(ConfigurationStore.get[A].get)
-
- def configured[A: Manifest] = ConfigurationStore.get[A].get
-
-}
-
-trait Configuration {
-
- final def configure[A <: AnyRef](tag: String)(f: => A) = {
- val a = f
- ConfigurationStore.put(tag, a)
- a
- }
-
- final def configure[A <: AnyRef](f: => A) = {
- val a = f
- ConfigurationStore.put(a.getClass.getName, a)
- a
- }
-
-}
\ No newline at end of file
diff --git a/maven/domain/src/main/scala/org/cakesolutions/akkapatterns/domain/customer.scala b/maven/domain/src/main/scala/org/cakesolutions/akkapatterns/domain/customer.scala
deleted file mode 100644
index 545d80b..0000000
--- a/maven/domain/src/main/scala/org/cakesolutions/akkapatterns/domain/customer.scala
+++ /dev/null
@@ -1,9 +0,0 @@
-package org.cakesolutions.akkapatterns.domain
-
-import java.util.UUID
-
-case class Customer(firstName: String, lastName: String,
- email: String, addresses: Seq[Address],
- id: UUID)
-
-case class Address(line1: String, line2: String, line3: String)
\ No newline at end of file
diff --git a/maven/domain/src/main/scala/org/cakesolutions/akkapatterns/domain/domain.scala b/maven/domain/src/main/scala/org/cakesolutions/akkapatterns/domain/domain.scala
deleted file mode 100644
index 66f2aa9..0000000
--- a/maven/domain/src/main/scala/org/cakesolutions/akkapatterns/domain/domain.scala
+++ /dev/null
@@ -1,9 +0,0 @@
-package org.cakesolutions.akkapatterns
-
-import java.util.UUID
-
-package object domain {
- type Identity = UUID
-
-
-}
diff --git a/maven/domain/src/main/scala/org/cakesolutions/akkapatterns/domain/user.scala b/maven/domain/src/main/scala/org/cakesolutions/akkapatterns/domain/user.scala
deleted file mode 100644
index 9b0546f..0000000
--- a/maven/domain/src/main/scala/org/cakesolutions/akkapatterns/domain/user.scala
+++ /dev/null
@@ -1,6 +0,0 @@
-package org.cakesolutions.akkapatterns.domain
-
-/**
- * @author janmachacek
- */
-case class User(id: Identity, username: String, password: String)
\ No newline at end of file
diff --git a/maven/main/pom.xml b/maven/main/pom.xml
deleted file mode 100644
index 9d2c5e2..0000000
--- a/maven/main/pom.xml
+++ /dev/null
@@ -1,94 +0,0 @@
-
-
-
- org.cakesolutions.akkapatterns
- server-parent
- 0.1.RELEASE-SNAPSHOT
- ../parent/pom.xml
-
- 4.0.0
- main
- jar
- Akka Patterns - Main
-
-
- org.cakesolutions.akkapatterns
- core
- 0.1.RELEASE-SNAPSHOT
-
-
- org.cakesolutions.akkapatterns
- api
- 0.1.RELEASE-SNAPSHOT
-
-
- org.cakesolutions.akkapatterns
- domain
- 0.1.RELEASE-SNAPSHOT
-
-
- org.cakesolutions.akkapatterns
- web
- 0.1.RELEASE-SNAPSHOT
-
-
- cc.spray
- spray-server
-
-
- cc.spray
- spray-io
-
-
- com.typesafe.akka
- akka-actor_${scala.version}
-
-
-
-
-
-
-
-
-
- maven-jar-plugin
- 2.3.1
-
-
-
- true
- true
- lib/
- com.orchestra.main.Main
-
-
- ${buildNumber} ${scmBranch}
-
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-dependency-plugin
-
-
- copy-dependencies
- prepare-package
-
- copy-dependencies
-
-
- ${project.build.directory}/lib
- false
- false
- true
-
-
-
-
-
-
-
diff --git a/maven/main/src/main/scala/org/cakesolutions/akkapatterns/main/Main.scala b/maven/main/src/main/scala/org/cakesolutions/akkapatterns/main/Main.scala
deleted file mode 100644
index 8f5c34b..0000000
--- a/maven/main/src/main/scala/org/cakesolutions/akkapatterns/main/Main.scala
+++ /dev/null
@@ -1,23 +0,0 @@
-package org.cakesolutions.akkapatterns.main
-
-import akka.actor.ActorSystem
-import org.cakesolutions.akkapatterns.domain.Configuration
-import org.cakesolutions.akkapatterns.core.Core
-import org.cakesolutions.akkapatterns.api.Api
-import org.cakesolutions.akkapatterns.web.Web
-
-object Main extends App {
- // -javaagent:/Users/janmachacek/.m2/repository/org/springframework/spring-instrument/3.1.1.RELEASE/spring-instrument-3.1.1.RELEASE.jar -Xmx512m -XX:MaxPermSize=256m
-
- implicit val system = ActorSystem("AkkaPatterns")
-
- class Application(val actorSystem: ActorSystem) extends Core with Api with Web with Configuration {
- }
-
- new Application(system)
-
- sys.addShutdownHook {
- system.shutdown()
- }
-
-}
diff --git a/maven/parent/pom.xml b/maven/parent/pom.xml
deleted file mode 100644
index 8ff952a..0000000
--- a/maven/parent/pom.xml
+++ /dev/null
@@ -1,270 +0,0 @@
-
-
- 4.0.0
- org.cakesolutions.akkapatterns
- server-parent
- pom
- 0.1.RELEASE-SNAPSHOT
- Akka Patterns - Parent
-
- scm:git:git@github.com:janm399/akka-patterns.git
-
-
- 2.9.2
- 3.0.1
- 2.0.1
- UTF-8
- 1.0-M2
-
-
-
-
-
-
- net.alchim31.maven
- scala-maven-plugin
- 3.1.0
-
-
-
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
- 2.3.2
-
-
- 1.6
-
-
-
- net.alchim31.maven
- scala-maven-plugin
-
-
-
- compile
- testCompile
-
-
-
-
-
- org.apache.maven.plugins
- maven-dependency-plugin
- 2.4
-
- true
- true
-
-
-
- process-test-classes-dependency-analyze
-
- process-test-classes
-
- analyze-only
-
-
-
-
-
- org.apache.maven.plugins
- maven-surefire-plugin
- 2.12
-
-
-
- **/*Spec.java
-
- 1
-
- ${settings.localRepository}/org/scala-lang/scala-reflect/${scala.version}/scala-reflect-${scala.version}.jar
-
-
-
-
- org.codehaus.mojo
- buildnumber-maven-plugin
- 1.1
-
-
- validate
-
- create
-
-
-
-
- true
- false
- false
- ${basedir}/buildnumber.properties
-
- true
- {0,date,yyyy-MM-dd HH:mm:ss}
-
- - timestamp
-
-
-
-
-
-
-
-
- net.alchim31.maven
- scala-maven-plugin
- 3.1.0
-
-
-
-
-
-
-
- org.scala-lang
- scala-library
- ${scala.version}
-
-
-
-
- com.typesafe.akka
- akka-kernel_${scala.version}
- ${akka.version}
-
-
- com.typesafe.akka
- akka-actor_${scala.version}
- ${akka.version}
-
-
- com.typesafe.akka
- akka-remote_${scala.version}
- ${akka.version}
-
-
- com.typesafe.akka
- akka-testkit_${scala.version}
- ${akka.version}
-
-
-
-
- cc.spray
- spray-server
- ${spray.version}
-
-
- cc.spray
- spray-io
- ${spray.version}
-
-
- cc.spray
- spray-can
- ${spray.version}
-
-
- cc.spray
- spray-base
- ${spray.version}
-
-
- cc.spray
- spray-util
- ${spray.version}
-
-
- org.jvnet
- mimepull
- 1.6
-
-
- org.parboiled
- parboiled-scala
- 1.0.2
-
-
-
-
- commons-dbcp
- commons-dbcp
- 1.4
-
-
- org.hsqldb
- hsqldb
- 2.2.4
-
-
-
-
- net.liftweb
- lift-json_2.9.1
- 2.4
-
-
- org.scala-lang
- scala-library
-
-
- org.scala-lang
- scalap
-
-
-
-
-
- junit
- junit-dep
- 4.8.2
- test
-
-
-
-
- org.apache.cassandra
- cassandra-thrift
- 1.1.1
-
-
- commons-pool
- commons-pool
- 1.6
-
-
-
-
-
- org.specs2
- specs2_${scala.version}
- 1.11
- test
-
-
- org.specs2
- specs2-scalaz-core_${scala.version}
- 6.0.1
- test
-
-
- com.typesafe.akka
- akka-testkit
- test
- ${akka.version}
-
-
-
-
-
-
-
- org.scala-lang
- scala-library
- ${scala.version}
-
-
-
diff --git a/maven/pom.xml b/maven/pom.xml
deleted file mode 100644
index f717781..0000000
--- a/maven/pom.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-
-
- 4.0.0
- org.cakesolutions.akkapatterns
- server
- pom
- 0.1.RELEASE-SNAPSHOT
- Akka Patterns
-
-
- janmachacek
- Jan Machacek
- janm@cakesolutions.net
-
- Project Admin
- Developer
-
- +0
-
-
-
-
- parent
-
-
- domain
-
- test
-
-
- core
- api
- web
-
-
- main
-
-
diff --git a/maven/test/pom.xml b/maven/test/pom.xml
deleted file mode 100644
index 3c7e4eb..0000000
--- a/maven/test/pom.xml
+++ /dev/null
@@ -1,55 +0,0 @@
-
-
-
- org.cakesolutions.akkapatterns
- server-parent
- 0.1.RELEASE-SNAPSHOT
- ../parent/pom.xml
-
- 4.0.0
- test
- jar
- Akka Patterns - Test support
-
-
- org.specs2
- specs2_${scala.version}
-
-
- org.cakesolutions.akkapatterns
- domain
- 0.1.RELEASE-SNAPSHOT
-
-
-
-
-
-
diff --git a/maven/test/src/main/resources/org/cakesolutions/akkapatterns/mongodb-base.js b/maven/test/src/main/resources/org/cakesolutions/akkapatterns/mongodb-base.js
deleted file mode 100644
index 3f50eac..0000000
--- a/maven/test/src/main/resources/org/cakesolutions/akkapatterns/mongodb-base.js
+++ /dev/null
@@ -1,14 +0,0 @@
-db.getCollectionNames().forEach(function (name) {
- if (name.indexOf("system.") == -1) db.getCollection(name).remove()
-});
-
-db.customers.save({
- "firstName":"Jan",
- "lastName":"Machacek",
- "email":"janm@cakesolutions.net",
- "id":UUID("00000000000000000000000000000000"),
- "addresses":[
- {"line1":"Magdalen Centre", "line2":"Robert Robinson Avenue", "line3":"Oxford"},
- {"line1":"Houldsworth Mill", "line2":"Houldsworth Street", "line3":"Reddish"}
- ]
-});
\ No newline at end of file
diff --git a/maven/test/src/main/scala/org/cakesolutions/akkapatterns/test/DefaultTestData.scala b/maven/test/src/main/scala/org/cakesolutions/akkapatterns/test/DefaultTestData.scala
deleted file mode 100644
index 9434d2d..0000000
--- a/maven/test/src/main/scala/org/cakesolutions/akkapatterns/test/DefaultTestData.scala
+++ /dev/null
@@ -1,20 +0,0 @@
-package org.cakesolutions.akkapatterns.test
-
-import org.cakesolutions.akkapatterns.domain.{Address, Customer}
-import java.util.UUID
-
-
-/**
- * @author janmachacek
- */
-trait DefaultTestData {
- val janMachacek = Customer("Jan", "Machacek", "janm@cakesolutions.net",
- Address("Magdalen Centre", "Robert Robinson Avenue", "Oxford") ::
- Address("Houldsworth Mill", "Houldsworth Street", "Reddish") :: Nil,
- UUID.fromString("00000000-0000-0000-0000-000000000000"))
-
- val joeBloggs = Customer("Joe", "Bloggs", "joe@cakesolutions.net",
- Address("123 Winding Road", "Cowley", "Oxford") :: Nil,
- UUID.fromString("00000000-0000-0000-0100-000000000000"))
-
-}
diff --git a/maven/test/src/main/scala/org/cakesolutions/akkapatterns/test/SpecConfiguration.scala b/maven/test/src/main/scala/org/cakesolutions/akkapatterns/test/SpecConfiguration.scala
deleted file mode 100644
index a3fa0b2..0000000
--- a/maven/test/src/main/scala/org/cakesolutions/akkapatterns/test/SpecConfiguration.scala
+++ /dev/null
@@ -1,11 +0,0 @@
-package org.cakesolutions.akkapatterns.test
-
-import org.cakesolutions.akkapatterns.domain.Configuration
-
-/**
- * @author janmachacek
- */
-trait SpecConfiguration extends Configuration {
-
-
-}
diff --git a/maven/web/pom.xml b/maven/web/pom.xml
deleted file mode 100644
index c526301..0000000
--- a/maven/web/pom.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-
-
-
- org.cakesolutions.akkapatterns
- server-parent
- 0.1.RELEASE-SNAPSHOT
- ../parent/pom.xml
-
- 4.0.0
- web
- jar
- Akka Patterns - Web server
-
-
- org.cakesolutions.akkapatterns
- api
- 0.1.RELEASE-SNAPSHOT
-
-
- org.cakesolutions.akkapatterns
- core
- 0.1.RELEASE-SNAPSHOT
-
-
-
- cc.spray
- spray-can
-
-
- cc.spray
- spray-server
-
-
- cc.spray
- spray-io
-
-
- com.typesafe.akka
- akka-actor_${scala.version}
-
-
-
-
diff --git a/maven/web/src/main/scala/org/cakesolutions/akkapatterns/web/boot.scala b/maven/web/src/main/scala/org/cakesolutions/akkapatterns/web/boot.scala
deleted file mode 100644
index ee35db8..0000000
--- a/maven/web/src/main/scala/org/cakesolutions/akkapatterns/web/boot.scala
+++ /dev/null
@@ -1,36 +0,0 @@
-package org.cakesolutions.akkapatterns.web
-
-import akka.actor.Props
-import cc.spray.can.server.HttpServer
-import cc.spray.io.IoWorker
-import cc.spray.io.pipelines.MessageHandlerDispatch
-import org.cakesolutions.akkapatterns.core.Core
-import org.cakesolutions.akkapatterns.api.Api
-import cc.spray.SprayCanRootService
-
-trait Web {
- this: Api with Core =>
-
- // every spray-can HttpServer (and HttpClient) needs an IoWorker for low-level network IO
- // (but several servers and/or clients can share one)
- val ioWorker = new IoWorker(actorSystem).start()
-
- // create and start the spray-can HttpServer, telling it that we want requests to be
- // handled by the root service actor
- val sprayCanServer = actorSystem.actorOf(
- Props(new HttpServer(ioWorker, MessageHandlerDispatch.SingletonHandler(
- actorSystem.actorOf(Props(new SprayCanRootService(rootService)))), actorSystem.settings.config)),
- name = "http-server"
- )
-
- // a running HttpServer can be bound, unbound and rebound
- // initially to need to tell it where to bind to
- sprayCanServer ! HttpServer.Bind("0.0.0.0", 8080)
-
- // finally we drop the api thread but hook the shutdown of
- // our IoWorker into the shutdown of the applications ActorSystem
- actorSystem.registerOnTermination {
- ioWorker.stop()
- }
-
-}
\ No newline at end of file
diff --git a/sbt/build.sbt b/sbt/build.sbt
deleted file mode 100644
index 8b2699a..0000000
--- a/sbt/build.sbt
+++ /dev/null
@@ -1,87 +0,0 @@
-import sbtrelease._
-
-/** Project */
-name := "akka-patterns"
-
-version := "1.0"
-
-organization := "org.cakesolutions"
-
-scalaVersion := "2.10.0"
-
-/** Shell */
-shellPrompt := { state => System.getProperty("user.name") + "> " }
-
-shellPrompt in ThisBuild := { state => Project.extract(state).currentRef.project + "> " }
-
-/** Dependencies */
-resolvers += "snapshots-repo" at "http://scala-tools.org/repo-snapshots"
-
-resolvers += "spray repo" at "http://repo.spray.io"
-
-resolvers += "spray nightlies" at "http://nightlies.spray.io"
-
-resolvers += "Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases/"
-
-resolvers += "Jasper Community" at "http://jasperreports.sourceforge.net/maven2"
-
-resolvers += "Sonatype OSS Releases" at "http://oss.sonatype.org/content/repositories/releases/"
-
-resolvers += "Sonatype OSS Snapshots" at "http://oss.sonatype.org/content/repositories/snapshots/"
-
-resolvers += "neo4j repo" at "http://m2.neo4j.org/content/repositories/releases/"
-
-resolvers += "neo4j snapshot repo" at "http://m2.neo4j.org/content/groups/public/"
-
-libraryDependencies <<= scalaVersion { scala_version =>
- val sprayVersion = "1.1-20130207"
- val akkaVersion = "2.1.0"
- val scalazVersion = "7.0.0-M8"
- Seq(
- "com.typesafe.akka" %% "akka-kernel" % akkaVersion,
- "com.typesafe.akka" %% "akka-actor" % akkaVersion,
- "io.spray" % "spray-can" % sprayVersion,
- "io.spray" % "spray-routing" % sprayVersion,
- "io.spray" % "spray-httpx" % sprayVersion,
- "io.spray" % "spray-util" % sprayVersion,
- "io.spray" % "spray-client" % sprayVersion,
- "com.aphelia" %% "amqp-client" % "1.0",
- "com.rabbitmq" % "amqp-client" % "2.8.1",
- "org.neo4j" % "neo4j" % "1.9-M02",
- "org.scalaz" %% "scalaz-effect" % scalazVersion,
- "org.scalaz" %% "scalaz-core" % scalazVersion,
- "org.cakesolutions" % "scalad_2.10" % "1.0",
- "net.sf.jasperreports" % "jasperreports" % "5.0.1",
- "org.apache.poi" % "poi" % "3.9",
- "io.spray" %% "spray-json" % "1.2.3",
- "javax.mail" % "mail" % "1.4.2",
- "org.specs2" % "classycle" % "1.4.1" % "test",
- "com.typesafe.akka" %% "akka-testkit" % akkaVersion % "test",
- "org.specs2" %% "specs2" % "1.13" % "test"
- )
-}
-
-/** Compilation */
-javaOptions += "-Xmx2G"
-
-scalacOptions ++= Seq("-deprecation", "-unchecked")
-
-maxErrors := 20
-
-pollInterval := 1000
-
-logBuffered := false
-
-cancelable := true
-
-testOptions := Seq(Tests.Filter(s =>
- Seq("Spec", "Suite", "Unit", "all").exists(s.endsWith(_)) &&
- !s.endsWith("FeaturesSpec") ||
- s.contains("UserGuide") ||
- s.contains("index") ||
- s.matches("org.specs2.guide.*")))
-
-/** Console */
-initialCommands in console := "import org.cakesolutions.akkapatterns._"
-
-seq(ScctPlugin.instrumentSettings : _*)
diff --git a/sbt/project/plugins.sbt b/sbt/project/plugins.sbt
deleted file mode 100644
index 610e31c..0000000
--- a/sbt/project/plugins.sbt
+++ /dev/null
@@ -1,9 +0,0 @@
-resolvers += "Sonatype snapshots" at "http://oss.sonatype.org/content/repositories/snapshots/"
-
-resolvers += "scct-github-repository" at "http://mtkopone.github.com/scct/maven-repo"
-
-addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.2.0-SNAPSHOT")
-
-addSbtPlugin("com.github.gseitz" % "sbt-release" % "0.6")
-
-addSbtPlugin("reaktor" % "sbt-scct" % "0.2-SNAPSHOT")
diff --git a/sbt/src/main/resources/org/cakesolutions/akkapatterns/mongodb-base.js b/sbt/src/main/resources/org/cakesolutions/akkapatterns/mongodb-base.js
deleted file mode 100644
index 3f50eac..0000000
--- a/sbt/src/main/resources/org/cakesolutions/akkapatterns/mongodb-base.js
+++ /dev/null
@@ -1,14 +0,0 @@
-db.getCollectionNames().forEach(function (name) {
- if (name.indexOf("system.") == -1) db.getCollection(name).remove()
-});
-
-db.customers.save({
- "firstName":"Jan",
- "lastName":"Machacek",
- "email":"janm@cakesolutions.net",
- "id":UUID("00000000000000000000000000000000"),
- "addresses":[
- {"line1":"Magdalen Centre", "line2":"Robert Robinson Avenue", "line3":"Oxford"},
- {"line1":"Houldsworth Mill", "line2":"Houldsworth Street", "line3":"Reddish"}
- ]
-});
\ No newline at end of file
diff --git a/sbt/src/main/scala/org/cakesolutions/akkapatterns/api/boot.scala b/sbt/src/main/scala/org/cakesolutions/akkapatterns/api/boot.scala
deleted file mode 100644
index b3f1aa8..0000000
--- a/sbt/src/main/scala/org/cakesolutions/akkapatterns/api/boot.scala
+++ /dev/null
@@ -1,28 +0,0 @@
-package org.cakesolutions.akkapatterns.api
-
-import akka.actor.{ActorRef, Props}
-import spray._
-import routing._
-import http.{StatusCodes, HttpResponse}
-import org.cakesolutions.akkapatterns.core.ServerCore
-import akka.util.Timeout
-
-trait Api extends RouteConcatenation {
- this: ServerCore =>
-
- val routes =
- new HomeService().route ~
- new CustomerService().route
-
- def rejectionHandler: PartialFunction[scala.List[Rejection], HttpResponse] = {
- case (rejections: List[Rejection]) => HttpResponse(StatusCodes.BadRequest)
- }
-
- val rootService = actorSystem.actorOf(Props(new RoutedHttpService(routes)))
-
-}
-
-trait DefaultTimeout {
- final implicit val timeout = Timeout(3000)
-
-}
\ No newline at end of file
diff --git a/sbt/src/main/scala/org/cakesolutions/akkapatterns/api/customer.scala b/sbt/src/main/scala/org/cakesolutions/akkapatterns/api/customer.scala
deleted file mode 100644
index d7c6c48..0000000
--- a/sbt/src/main/scala/org/cakesolutions/akkapatterns/api/customer.scala
+++ /dev/null
@@ -1,37 +0,0 @@
-package org.cakesolutions.akkapatterns.api
-
-import akka.actor.ActorSystem
-import spray.routing.Directives
-import spray.httpx.marshalling.MetaMarshallers
-import org.cakesolutions.akkapatterns.domain.{CustomerFormats, Customer}
-import org.cakesolutions.akkapatterns.core.UpdateCustomer
-import spray.httpx.SprayJsonSupport
-
-/**
- * @author janmachacek
- */
-class CustomerService(implicit val actorSystem: ActorSystem) extends Directives with MetaMarshallers with DefaultTimeout
- with DefaultAuthenticationDirectives with CustomerFormats with SprayJsonSupport {
- import akka.pattern.ask
- import concurrent.ExecutionContext.Implicits.global
-
- def customerActor = actorSystem.actorFor("/user/application/customer")
-
- val route =
- path("customers" / JavaUUID) { id =>
- get {
- complete {
- "Just you wait"
- }
- } ~
- post {
- authenticate(validCustomer) { ud =>
- // if we authenticated only validUser or validSuperuser
- handleWith { customer: Customer =>
- (customerActor ? UpdateCustomer(ud, customer)).mapTo[Customer]
- // then this call would not type-check!
- }
- }
- }
- }
-}
diff --git a/sbt/src/main/scala/org/cakesolutions/akkapatterns/api/home.scala b/sbt/src/main/scala/org/cakesolutions/akkapatterns/api/home.scala
deleted file mode 100644
index 09981e5..0000000
--- a/sbt/src/main/scala/org/cakesolutions/akkapatterns/api/home.scala
+++ /dev/null
@@ -1,41 +0,0 @@
-package org.cakesolutions.akkapatterns.api
-
-import java.net.InetAddress
-import akka.actor.ActorSystem
-import akka.pattern.ask
-import spray.httpx.marshalling.MetaMarshallers
-import spray.routing.Directives
-import org.cakesolutions.akkapatterns.core.{ GetImplementation, Implementation }
-import java.util.Date
-import scala.concurrent.ExecutionContext.Implicits._
-import spray.json.DefaultJsonProtocol
-import spray.httpx.SprayJsonSupport
-
-trait HomerServiceMarshalling extends DefaultJsonProtocol {
-
- implicit val ImplementationFormat = jsonFormat3(Implementation)
- implicit val SystemInfoFormat = jsonFormat3(SystemInfo)
-
-}
-
-case class SystemInfo(implementation: Implementation, host: String, timestamp: Long)
-
-class HomeService(implicit val actorSystem: ActorSystem) extends Directives with HomerServiceMarshalling with MetaMarshallers with SprayJsonSupport with DefaultTimeout {
-
- def applicationActor = actorSystem.actorFor("/user/application")
-
- val route = {
- path(Slash) {
- get {
- complete {
- val f =(applicationActor ? GetImplementation()).mapTo[Implementation].map {
- SystemInfo(_, InetAddress.getLocalHost.getCanonicalHostName, new Date().getTime)
- }
-
- f
- }
- }
- }
- }
-
-}
diff --git a/sbt/src/main/scala/org/cakesolutions/akkapatterns/api/marshalling.scala b/sbt/src/main/scala/org/cakesolutions/akkapatterns/api/marshalling.scala
deleted file mode 100644
index 8ef426f..0000000
--- a/sbt/src/main/scala/org/cakesolutions/akkapatterns/api/marshalling.scala
+++ /dev/null
@@ -1,60 +0,0 @@
-package org.cakesolutions.akkapatterns.api
-
-import org.cakesolutions.akkapatterns.domain._
-import org.cakesolutions.akkapatterns.core._
-import spray.json.DefaultJsonProtocol
-import org.cakesolutions.akkapatterns.UuidFormats
-import spray.httpx.SprayJsonSupport
-import spray.httpx.marshalling.{CollectingMarshallingContext, MetaMarshallers, Marshaller}
-import spray.http.{HttpEntity, StatusCode}
-
-trait AllFormats extends UserFormats {
-
- implicit val NotRegisteredUserFormat = jsonFormat1(NotRegisteredUser)
- implicit val RegisteredUserFormat = jsonFormat1(RegisteredUser)
-
- implicit val AddressFormat = jsonFormat3(Address)
- implicit val CustomerFormat = jsonFormat5(Customer)
- implicit val RegisterCustomerFormat = jsonFormat2(RegisterCustomer)
- implicit val NotRegisteredCustomerFormat = jsonFormat1(NotRegisteredCustomer)
- implicit val RegisteredCustomerFormat = jsonFormat2(RegisteredCustomer)
-
-}
-
-trait Marshalling extends DefaultJsonProtocol with AllFormats with UuidFormats with SprayJsonSupport with MetaMarshallers {
- /**
- * Function that computers a HTTP status given value of type ``A``
- *
- * @tparam A the type a
- */
- type ErrorSelector[A] = A => StatusCode
-
- /**
- * Marshaller that uses some ``ErrorSelector`` for the value on the left to indicate that it is an error, even though
- * the error response should still be marshalled and returned to the caller.
- *
- * This is useful when you need to return validation or other processing errors, but need a bit more information than
- * just ``HTTP status 422`` (or, even worse simply ``400``).
- *
- * @param ma the marshaller for ``A`` (the error)
- * @param mb the marshaller for ``B`` (the success)
- * @param esa the error selector for ``A`` so that we know what HTTP status to return for the value on the left
- * @tparam A the type on the left
- * @tparam B the type on the right
- * @return the marshaller instance
- */
- implicit def errorSelectingEitherMarshaller[A, B](implicit ma: Marshaller[A], mb: Marshaller[B], esa: ErrorSelector[A]) =
- Marshaller[Either[A, B]] { (value, ctx) =>
- value match {
- case Left(a) =>
- val mc = new CollectingMarshallingContext()
- ma(a, mc)
- ctx.handleError(ErrorResponseException(esa(a), mc.entity))
- case Right(b) =>
- mb(b, ctx)
- }
- }
-
-}
-
-case class ErrorResponseException(responseStatus: StatusCode, response: Option[HttpEntity]) extends RuntimeException
diff --git a/sbt/src/main/scala/org/cakesolutions/akkapatterns/api/services.scala b/sbt/src/main/scala/org/cakesolutions/akkapatterns/api/services.scala
deleted file mode 100644
index 45bf125..0000000
--- a/sbt/src/main/scala/org/cakesolutions/akkapatterns/api/services.scala
+++ /dev/null
@@ -1,29 +0,0 @@
-package org.cakesolutions.akkapatterns.api
-
-import akka.actor.Actor
-import spray.routing._
-import util.control.NonFatal
-import spray.http.StatusCodes._
-import spray.util.LoggingContext
-
-/**
- * @author janmachacek
- */
-class RoutedHttpService(route: Route) extends Actor with HttpService {
-
- implicit def actorRefFactory = context
-
- implicit val handler = ExceptionHandler.fromPF {
- case NonFatal(ErrorResponseException(statusCode, entity)) => ctx =>
- ctx.complete(statusCode, entity)
-
- case NonFatal(e) => ctx =>
- ctx.complete(InternalServerError)
- }
-
-
- def receive = {
- runRoute(route)(handler, RejectionHandler.Default, context, RoutingSettings.Default, LoggingContext.fromActorRefFactory)
- }
-
-}
diff --git a/sbt/src/main/scala/org/cakesolutions/akkapatterns/api/user.scala b/sbt/src/main/scala/org/cakesolutions/akkapatterns/api/user.scala
deleted file mode 100644
index 57f41cc..0000000
--- a/sbt/src/main/scala/org/cakesolutions/akkapatterns/api/user.scala
+++ /dev/null
@@ -1,27 +0,0 @@
-package org.cakesolutions.akkapatterns.api
-
-import akka.actor.ActorSystem
-import spray.httpx.marshalling.MetaMarshallers
-import spray.routing.Directives
-import org.cakesolutions.akkapatterns.domain.{UserFormats, User}
-import spray.httpx.SprayJsonSupport
-
-/**
- * @author janmachacek
- */
-class UserService(implicit val actorSystem: ActorSystem) extends Directives with DefaultTimeout with UserFormats with MetaMarshallers with SprayJsonSupport {
- def userActor = actorSystem.actorFor("/user/application/user")
-
- val route =
- path("user" / "register") {
- post {
- entity(as[User]) { user =>
- complete {
- // (userActor ? RegisteredUser(user)).mapTo[Either[NotRegisteredUser, RegisteredUser]]
- "Wait a bit!"
- }
- }
- }
- }
-
-}
diff --git a/sbt/src/main/scala/org/cakesolutions/akkapatterns/core/application.scala b/sbt/src/main/scala/org/cakesolutions/akkapatterns/core/application.scala
deleted file mode 100644
index 6b69e70..0000000
--- a/sbt/src/main/scala/org/cakesolutions/akkapatterns/core/application.scala
+++ /dev/null
@@ -1,35 +0,0 @@
-package org.cakesolutions.akkapatterns.core
-
-import akka.actor.{Props, Actor}
-
-case class GetImplementation()
-case class Implementation(title: String, version: String, build: String)
-
-class ApplicationActor extends Actor {
-
- def receive = {
- case GetImplementation() =>
- val title = "Akka-Patterns"
- val version = "1.0"
- val build = "1.0"
-
- sender ! Implementation(title, version, build)
-
- case Start() =>
- val messageDelivery = context.actorOf(Props[MessageDeliveryActor], "messageDelivery")
- context.actorOf(Props(new CustomerActor(messageDelivery)), "customer")
- context.actorOf(Props(new UserActor(messageDelivery)), "user")
-
- new SanityChecks {
- sender ! (if (ensureSanity) Started() else InmatesAreRunningTheAsylum)
- }
-
- /*
- * Stops this actor and all the child actors.
- */
- case Stop() =>
- context.children.foreach(context.stop _)
-
- }
-
-}
diff --git a/sbt/src/main/scala/org/cakesolutions/akkapatterns/core/customer.scala b/sbt/src/main/scala/org/cakesolutions/akkapatterns/core/customer.scala
deleted file mode 100644
index 1ca5738..0000000
--- a/sbt/src/main/scala/org/cakesolutions/akkapatterns/core/customer.scala
+++ /dev/null
@@ -1,65 +0,0 @@
-package org.cakesolutions.akkapatterns.core
-
-import akka.actor.{ActorRef, Actor}
-import org.cakesolutions.akkapatterns.domain._
-
-/**
- * Registers a customer and a user. After registering, we have a user account for the given customer.
- *
- * @param customer the customer
- * @param user the user
- */
-case class RegisterCustomer(customer: Customer, user: User)
-
-/**
- * Reply to successful customer registration
- * @param customer the newly registered customer
- * @param user the newly registered user
- */
-case class RegisteredCustomer(customer: Customer, user: User)
-
-/**
- * Reply to unsuccessful customer registration
- * @param code the error code for the failure reason
- */
-case class NotRegisteredCustomer(code: String) extends ApplicationFailure
-
-/**
- * Update the customer details
- *
- * @param userDetail the user making the call
- * @param customer the customer to be updated
- */
-case class UpdateCustomer(userDetail: UserDetailT[CustomerUserKind], customer: Customer)
-
-/**
- * CRUD operations for the [[org.cakesolutions.akkapatterns.domain.Customer]]s
- */
-trait CustomerOperations {
- // def customers: MongoCollection
-
- def getCustomer(id: CustomerReference): Option[Customer] = None
-
- def findAllCustomers(): List[Customer] = List()
-
- def insertCustomer(customer: Customer): Customer = {
- //customers += serialize(customer)
- customer
- }
-
- def registerCustomer(customer: Customer)(ru: RegisteredUser): Either[ApplicationFailure, RegisteredCustomer] = {
- //customers += serialize(customer)
- Right(RegisteredCustomer(customer, ru.user))
- }
-
-}
-
-/**
- * Performs the customer operations
- */
-class CustomerActor(messageDelivery: ActorRef) extends Actor with CustomerOperations with UserOperations {
-
- def receive = {
- case _ => // TODO: complete me by moving me to Scalad
- }
-}
diff --git a/sbt/src/main/scala/org/cakesolutions/akkapatterns/domain/customer.scala b/sbt/src/main/scala/org/cakesolutions/akkapatterns/domain/customer.scala
deleted file mode 100644
index f538593..0000000
--- a/sbt/src/main/scala/org/cakesolutions/akkapatterns/domain/customer.scala
+++ /dev/null
@@ -1,17 +0,0 @@
-package org.cakesolutions.akkapatterns.domain
-
-import spray.json.DefaultJsonProtocol
-import org.cakesolutions.akkapatterns.UuidFormats
-
-case class Customer(id: CustomerReference,
- firstName: String, lastName: String,
- email: String, addresses: Seq[Address])
-
-case class Address(line1: String, line2: String, line3: String)
-
-trait CustomerFormats extends DefaultJsonProtocol with UuidFormats {
-
- implicit val AddressFormat = jsonFormat3(Address)
- implicit val CustomerFormat = jsonFormat5(Customer)
-
-}
\ No newline at end of file
diff --git a/sbt/src/main/scala/org/cakesolutions/akkapatterns/io.scala b/sbt/src/main/scala/org/cakesolutions/akkapatterns/io.scala
deleted file mode 100644
index f9e043b..0000000
--- a/sbt/src/main/scala/org/cakesolutions/akkapatterns/io.scala
+++ /dev/null
@@ -1,59 +0,0 @@
-package org.cakesolutions.akkapatterns
-
-import akka.actor.{Props, Actor, ActorSystem}
-import spray.io.IOExtension
-import com.typesafe.config.ConfigFactory
-import spray.client.HttpClient
-import com.aphelia.amqp.ConnectionOwner
-import com.aphelia.amqp.Amqp.ExchangeParameters
-import com.rabbitmq.client.ConnectionFactory
-
-/**
- * Instantiates & provides access to Spray's ``IOBridge``.
- *
- * @author janmachacek
- */
-trait HttpIO {
- implicit def actorSystem: ActorSystem
-
- lazy val ioBridge = IOExtension(actorSystem).ioBridge() // new IOBridge(actorSystem).start()
-
- lazy val httpClient = actorSystem.actorOf(
- Props(new HttpClient(ConfigFactory.parseString("spray.can.client.ssl-encryption = on")))
- )
-
-}
-
-/**
- * Convenience ``HttpIO`` implementation that can be mixed in to actors.
- */
-trait ActorHttpIO extends HttpIO {
- this: Actor =>
-
- final implicit def actorSystem = context.system
-}
-
-/**
- * Provides connection & access to the AMQP broker
- */
-trait AmqpIO {
- implicit def actorSystem: ActorSystem
-
- // prepare the AMQP connection factory
- final lazy val connectionFactory = new ConnectionFactory(); connectionFactory.setHost("localhost")
- // connect to the AMQP exchange
- final lazy val amqpExchange = ExchangeParameters(name = "amq.direct", exchangeType = "", passive = true)
-
- // create a "connection owner" actor, which will try and reconnect automatically if the connection ins lost
- val connection = actorSystem.actorOf(Props(new ConnectionOwner(connectionFactory)))
-
-}
-
-/**
- * Convenience ``AmqpIO`` implementation that can be mixed in to actors.
- */
-trait ActorAmqpIO extends AmqpIO {
- this: Actor =>
- final implicit def actorSystem = context.system
-
-}
\ No newline at end of file
diff --git a/sbt/src/main/scala/org/cakesolutions/akkapatterns/marshalling.scala b/sbt/src/main/scala/org/cakesolutions/akkapatterns/marshalling.scala
deleted file mode 100644
index 0aa00a4..0000000
--- a/sbt/src/main/scala/org/cakesolutions/akkapatterns/marshalling.scala
+++ /dev/null
@@ -1,14 +0,0 @@
-package org.cakesolutions.akkapatterns
-
-import spray.json._
-import java.util.UUID
-
-trait UuidFormats {
- implicit object UuidJsonFormat extends JsonFormat[UUID] {
- def write(x: UUID) = JsString(x toString ())
- def read(value: JsValue) = value match {
- case JsString(x) => UUID.fromString(x)
- case x => deserializationError("Expected UUID as JsString, but got " + x)
- }
- }
-}
diff --git a/sbt/src/main/scala/org/cakesolutions/akkapatterns/web/boot.scala b/sbt/src/main/scala/org/cakesolutions/akkapatterns/web/boot.scala
deleted file mode 100644
index 369005b..0000000
--- a/sbt/src/main/scala/org/cakesolutions/akkapatterns/web/boot.scala
+++ /dev/null
@@ -1,34 +0,0 @@
-package org.cakesolutions.akkapatterns.web
-
-import org.cakesolutions.akkapatterns.core.ServerCore
-import org.cakesolutions.akkapatterns.api.Api
-import spray.io.{SingletonHandler, IOBridge}
-import spray.can.server.HttpServer
-import akka.actor.Props
-import org.cakesolutions.akkapatterns.HttpIO
-
-trait Web extends HttpIO {
- this: Api with ServerCore =>
-
- // every spray-can HttpServer (and HttpClient) needs an IOBridge for low-level network IO
- // (but several servers and/or clients can share one)
- // val ioBridge = new IOBridge(actorSystem).start()
-
- // create and start the spray-can HttpServer, telling it that
- // we want requests to be handled by our singleton service actor
- val httpServer = actorSystem.actorOf(
- Props(new HttpServer(SingletonHandler(rootService))),
- name = "http-server"
- )
-
- // a running HttpServer can be bound, unbound and rebound
- // initially to need to tell it where to bind to
- httpServer ! HttpServer.Bind("localhost", 8080)
-
- // finally we drop the main thread but hook the shutdown of
- // our IOBridge into the shutdown of the applications ActorSystem
- actorSystem.registerOnTermination {
- // ioBridge ! Stop
- }
-
-}
diff --git a/sbt/src/test/scala/org/cakesolutions/akkapatterns/UserActorSpec.scala b/sbt/src/test/scala/org/cakesolutions/akkapatterns/UserActorSpec.scala
deleted file mode 100644
index 938189a..0000000
--- a/sbt/src/test/scala/org/cakesolutions/akkapatterns/UserActorSpec.scala
+++ /dev/null
@@ -1,26 +0,0 @@
-package org.cakesolutions.akkapatterns
-
-import core.{GetUserByUsername, UserActor, SanityChecks}
-import org.specs2.mutable.Specification
-import akka.actor.ActorSystem
-import akka.testkit.{ImplicitSender, TestActorRef, TestKit}
-
-/**
- * @author janmachacek
- */
-class UserActorSpec extends TestKit(ActorSystem()) with Specification with SanityChecks with ImplicitSender {
- sequential
- ensureSanity
-
- val actor = TestActorRef(new UserActor(testActor))
-
- "Basic user operations" should {
-
- "Find the root user" in {
- actor ! GetUserByUsername("root")
- expectMsg(Some(RootUser))
- success
- }
- }
-
-}
diff --git a/sbt/src/test/scala/org/cakesolutions/akkapatterns/specifications.scala b/sbt/src/test/scala/org/cakesolutions/akkapatterns/specifications.scala
deleted file mode 100644
index 83cc46b..0000000
--- a/sbt/src/test/scala/org/cakesolutions/akkapatterns/specifications.scala
+++ /dev/null
@@ -1,20 +0,0 @@
-package org.cakesolutions.akkapatterns
-
-import akka.testkit.TestKit
-import akka.actor.ActorSystem
-import com.typesafe.config.ConfigFactory
-
-/**
- * @author janmachacek
- */
-trait Timers {
-
- def timed[U](f: => U): Long = {
- val startTime = System.currentTimeMillis
- f
- System.currentTimeMillis - startTime
- }
-
-}
-
-class ConfiguredTestKit extends TestKit(ActorSystem("Test", ConfigFactory.load("server.conf")))
\ No newline at end of file
diff --git a/sbt/src/test/scala/org/cakesolutions/akkapatterns/testdata.scala b/sbt/src/test/scala/org/cakesolutions/akkapatterns/testdata.scala
deleted file mode 100644
index b6d287f..0000000
--- a/sbt/src/test/scala/org/cakesolutions/akkapatterns/testdata.scala
+++ /dev/null
@@ -1,16 +0,0 @@
-package org.cakesolutions.akkapatterns
-
-import domain._
-import java.util.UUID
-
-/**
- * @author janmachacek
- */
-trait TestData {
-
- object Users {
-
- def newUser(username: String): User = User(UUID.randomUUID(), username, "", "janm@cakesolutions.net", None, "F" + username, "L" + username, SuperuserKind).resetPassword("password")
- }
-
-}
diff --git a/sbt/version.sbt b/sbt/version.sbt
deleted file mode 100644
index 5e469a2..0000000
--- a/sbt/version.sbt
+++ /dev/null
@@ -1,2 +0,0 @@
-
-version in ThisBuild := "1.0"
diff --git a/sbt/src/main/scala/org/cakesolutions/akkapatterns/api/authentication.scala b/server/api/src/main/scala/org/cakesolutions/akkapatterns/api/authentication.scala
similarity index 67%
rename from sbt/src/main/scala/org/cakesolutions/akkapatterns/api/authentication.scala
rename to server/api/src/main/scala/org/cakesolutions/akkapatterns/api/authentication.scala
index ad90200..33fa306 100644
--- a/sbt/src/main/scala/org/cakesolutions/akkapatterns/api/authentication.scala
+++ b/server/api/src/main/scala/org/cakesolutions/akkapatterns/api/authentication.scala
@@ -1,12 +1,14 @@
package org.cakesolutions.akkapatterns.api
-import spray.routing.{RequestContext, AuthenticationFailedRejection, AuthenticationRequiredRejection}
+import spray.routing.{HttpService, RequestContext, AuthenticationFailedRejection, AuthenticationRequiredRejection}
import concurrent.Future
import spray.routing.authentication.Authentication
import java.util.UUID
-import akka.actor.ActorSystem
+import akka.actor.ActorRef
import org.cakesolutions.akkapatterns.domain._
import org.cakesolutions.akkapatterns.core.authentication.TokenCheck
+import akka.util.Timeout
+import spray.http.HttpRequest
/**
* Mix in this trait to get the authentication directive. The ``validUser`` function can be used in Spray's
@@ -30,8 +32,7 @@ import org.cakesolutions.akkapatterns.core.authentication.TokenCheck
* @author janmachacek
*/
trait AuthenticationDirectives {
-
- import concurrent.ExecutionContext.Implicits.global
+ this: HttpService =>
/**
* @return a `User` that has been previously identified with the `Token` we have been given.
@@ -44,15 +45,32 @@ trait AuthenticationDirectives {
*/
private def doValidUser[A <: UserKind](map: UserDetailT[_] => Authentication[UserDetailT[A]]): RequestContext => Future[Authentication[UserDetailT[A]]] = {
ctx: RequestContext =>
- val header = ctx.request.headers.find(_.name == "x-token")
- if (header.isEmpty)
- Future(Left(AuthenticationRequiredRejection("https", "zoetic")))
- else doAuthenticate(UUID.fromString(header.get.value)).map {
- case Some(user) => map(user)
- case None => Left(AuthenticationFailedRejection("Zoetic"))
+ getToken(ctx.request) match {
+ case None => Future(Left(AuthenticationRequiredRejection("https", "patterns")))
+ case Some(token) => doAuthenticate(token) .map {
+ case Some(user) => map(user)
+ case None => Left(AuthenticationFailedRejection("Patterns"))
+ }
}
}
+ // http://en.wikipedia.org/wiki/Universally_unique_identifier
+ val uuidRegex = """^\p{XDigit}{8}(-\p{XDigit}{4}){3}-\p{XDigit}{12}$""".r
+ def isUuid(token: String) = token.length == 36 && uuidRegex.findPrefixOf(token).isDefined
+
+ def getToken(request: HttpRequest): Option[UUID] = {
+ val query = request.queryParams.get("token")
+ if (query.isDefined && isUuid(query.get))
+ Some(UUID.fromString(query.get))
+ else {
+ val header = request.headers.find(_.name == "x-token")
+ if (header.isDefined && isUuid(header.get.value))
+ Some(UUID.fromString(header.get.value))
+ else
+ None
+ }
+ }
+
/**
* Checks that the token represents a valid user; i.e. someone is logged in. We make no assumptions about the roles
*
@@ -88,16 +106,13 @@ trait AuthenticationDirectives {
}
}
-/**
- * provides a default implementation for Authentication Directives
- */
-trait DefaultAuthenticationDirectives extends AuthenticationDirectives with DefaultTimeout {
- this: { def actorSystem: ActorSystem } =>
- import concurrent.ExecutionContext.Implicits.global
- import akka.pattern.ask
+trait DefaultAuthenticationDirectives extends AuthenticationDirectives {
+ this: HttpService =>
- def loginActor = actorSystem.actorFor("/user/application/authentication/login")
+ import akka.pattern.ask
+ implicit val timeout: Timeout
+ def loginActor: ActorRef
override def doAuthenticate(token: UUID) = (loginActor ? TokenCheck(token)).mapTo[Option[UserDetailT[_]]]
diff --git a/server/api/src/main/scala/org/cakesolutions/akkapatterns/api/boot.scala b/server/api/src/main/scala/org/cakesolutions/akkapatterns/api/boot.scala
new file mode 100644
index 0000000..05fe0a8
--- /dev/null
+++ b/server/api/src/main/scala/org/cakesolutions/akkapatterns/api/boot.scala
@@ -0,0 +1,41 @@
+package org.cakesolutions.akkapatterns.api
+
+import akka.actor.Actor
+import spray._
+import routing._
+import org.cakesolutions.akkapatterns.core.CoreActorRefs
+import akka.util.Timeout
+import org.cakesolutions.akkapatterns.domain.Configured
+
+class Api extends Actor with HttpServiceActor
+ with CoreActorRefs
+ with FailureHandling
+ with Tracking with Configured
+ with EndpointMarshalling
+ with DefaultAuthenticationDirectives
+ with CustomerService
+ with HomeService
+ with UserService
+ {
+
+ // used by the Akka ask pattern
+ implicit val timeout = Timeout(10000)
+
+ // lets the CoreActorRef find the actor system used by Spray
+ // (this could potentially be a separate system)
+ def system = actorSystem
+
+ val routes =
+ customerRoute ~
+ homeRoute ~
+ userRoute
+
+ def receive = runRoute (
+ handleRejections(rejectionHandler)(
+ handleExceptions(exceptionHandler)(
+ trackRequestResponse(routes)
+ )
+ )
+ )
+
+}
diff --git a/server/api/src/main/scala/org/cakesolutions/akkapatterns/api/customer.scala b/server/api/src/main/scala/org/cakesolutions/akkapatterns/api/customer.scala
new file mode 100644
index 0000000..a0c3db3
--- /dev/null
+++ b/server/api/src/main/scala/org/cakesolutions/akkapatterns/api/customer.scala
@@ -0,0 +1,37 @@
+package org.cakesolutions.akkapatterns.api
+
+import spray.routing.HttpService
+import org.cakesolutions.akkapatterns.domain.Customer
+import org.cakesolutions.akkapatterns.core.CustomerController
+import akka.util.Timeout
+import scala.concurrent.Future
+import java.util.Date
+
+trait CustomerService extends HttpService {
+ this: EndpointMarshalling with AuthenticationDirectives =>
+
+ protected val customerController = new CustomerController
+
+ val customerRoute =
+ path("customers" / JavaUUID) { id =>
+ get {
+ complete {
+ // when using controllers, we have to explicitly create the Future here
+ // it is not necessary to add the T information, but it helps with API documentation.
+ Future[Customer] {
+ customerController.get(id)
+ }
+ }
+ } ~
+ authenticate(validCustomer) { ud =>
+ post {
+ handleWith { customer: Customer =>
+ // if we authenticated only validUser or validSuperuser
+ Future[Customer] {
+ customerController.update(ud, customer)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/server/api/src/main/scala/org/cakesolutions/akkapatterns/api/home.scala b/server/api/src/main/scala/org/cakesolutions/akkapatterns/api/home.scala
new file mode 100644
index 0000000..049d649
--- /dev/null
+++ b/server/api/src/main/scala/org/cakesolutions/akkapatterns/api/home.scala
@@ -0,0 +1,31 @@
+package org.cakesolutions.akkapatterns.api
+
+import java.net.InetAddress
+import org.cakesolutions.akkapatterns.core.{GetImplementation, Implementation}
+import java.util.Date
+import spray.routing.HttpService
+import akka.util.Timeout
+import akka.actor.ActorRef
+
+case class SystemInfo(implementation: Implementation, host: String, timestamp: Date)
+
+trait HomeService extends HttpService {
+ this: EndpointMarshalling with AuthenticationDirectives =>
+
+ import akka.pattern.ask
+ implicit val timeout: Timeout
+ def applicationActor: ActorRef
+
+ val homeRoute = {
+ path(Slash) {
+ get {
+ complete {
+ (applicationActor ? GetImplementation()).mapTo[Implementation].map {
+ SystemInfo(_, InetAddress.getLocalHost.getCanonicalHostName, new Date)
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/server/api/src/main/scala/org/cakesolutions/akkapatterns/api/marshalling.scala b/server/api/src/main/scala/org/cakesolutions/akkapatterns/api/marshalling.scala
new file mode 100644
index 0000000..bb507e5
--- /dev/null
+++ b/server/api/src/main/scala/org/cakesolutions/akkapatterns/api/marshalling.scala
@@ -0,0 +1,61 @@
+package org.cakesolutions.akkapatterns.api
+
+import org.cakesolutions.akkapatterns.domain._
+import org.cakesolutions.akkapatterns.core._
+import spray.json.DefaultJsonProtocol
+import spray.httpx.SprayJsonSupport
+import spray.httpx.marshalling.{CollectingMarshallingContext, MetaMarshallers, Marshaller}
+import spray.http.{HttpEntity, StatusCode}
+import org.cakesolutions.scalad.mongo.sprayjson.{DateMarshalling, UuidMarshalling}
+
+// Pure boilerplate - please create a code generator (I'll be your *best* friend!)
+trait ApiMarshalling extends DefaultJsonProtocol
+ with UuidMarshalling with DateMarshalling {
+ this: UserFormats =>
+
+ implicit val NotRegisteredUserFormat = jsonFormat1(NotRegisteredUser)
+ implicit val RegisteredUserFormat = jsonFormat1(RegisteredUser)
+
+ implicit val ImplementationFormat = jsonFormat3(Implementation)
+ implicit val SystemInfoFormat = jsonFormat3(SystemInfo)
+}
+
+case class ErrorResponseException(responseStatus: StatusCode, response: Option[HttpEntity]) extends Exception
+
+trait EitherErrorMarshalling {
+
+ /**
+ * Marshaller that uses some ``ErrorSelector`` for the value on the left to indicate that it is an error, even though
+ * the error response should still be marshalled and returned to the caller.
+ *
+ * This is useful when you need to return validation or other processing errors, but need a bit more information than
+ * just ``HTTP status 422`` (or, even worse simply ``400``).
+ *
+ * Bring an implicit instance of this method into scope of your HttpServices to get the status code.
+ *
+ * @param status the status code to return for errors.
+ * @param ma the marshaller for ``A`` (the error)
+ * @param mb the marshaller for ``B`` (the success)
+ * @tparam A the type on the left
+ * @tparam B the type on the right
+ * @return the marshaller instance
+ */
+ def errorSelectingEitherMarshaller[A, B](status: StatusCode)
+ (implicit ma: Marshaller[A], mb: Marshaller[B]) =
+ Marshaller[Either[A, B]] {
+ (value, ctx) =>
+ value match {
+ case Left(a) =>
+ val mc = new CollectingMarshallingContext()
+ ma(a, mc)
+ ctx.handleError(ErrorResponseException(status, mc.entity))
+ case Right(b) =>
+ mb(b, ctx)
+ }
+ }
+}
+
+trait EndpointMarshalling extends MetaMarshallers with SprayJsonSupport
+ with ApiMarshalling
+ with UserFormats with CustomerFormats
+ with EitherErrorMarshalling
diff --git a/server/api/src/main/scala/org/cakesolutions/akkapatterns/api/services.scala b/server/api/src/main/scala/org/cakesolutions/akkapatterns/api/services.scala
new file mode 100644
index 0000000..400986b
--- /dev/null
+++ b/server/api/src/main/scala/org/cakesolutions/akkapatterns/api/services.scala
@@ -0,0 +1,48 @@
+package org.cakesolutions.akkapatterns.api
+
+import spray.http.StatusCodes._
+import spray.http._
+import spray.routing._
+import spray.util.LoggingContext
+
+/** Provides a hook to catch exceptions and rejections from routes, allowing custom
+ * responses to be provided, logs to be captured, and potentially remedial actions.
+ *
+ * Note that this is not marshalled, but it is possible to do so allowing for a fully
+ * JSON API (e.g. see how Foursquare do it).
+ */
+trait FailureHandling {
+ this: HttpService =>
+
+ // For Spray > 1.1-M7 use routeRouteResponse
+ // see https://groups.google.com/d/topic/spray-user/zA_KR4OBs1I/discussion
+ def rejectionHandler: RejectionHandler = RejectionHandler.Default
+
+ def exceptionHandler(implicit log: LoggingContext) = ExceptionHandler.fromPF {
+
+ case e: IllegalArgumentException => ctx =>
+ loggedFailureResponse(ctx, e,
+ message = "The server was asked a question that didn't make sense: " + e.getMessage,
+ error = NotAcceptable)
+
+ case e: NoSuchElementException => ctx =>
+ loggedFailureResponse(ctx, e,
+ message = "The server is missing some information. Try again in a few moments.",
+ error = NotFound)
+
+ case t: Throwable => ctx =>
+ // note that toString here may expose information and cause a security leak, so don't do it.
+ loggedFailureResponse(ctx, t)
+ }
+
+ private def loggedFailureResponse(ctx: RequestContext,
+ thrown: Throwable,
+ message: String = "The server is having problems.",
+ error: StatusCode = InternalServerError)
+ (implicit log: LoggingContext)
+ {
+ log.error(thrown, ctx.request.toString())
+ ctx.complete(error, message)
+ }
+
+}
diff --git a/server/api/src/main/scala/org/cakesolutions/akkapatterns/api/tracking.scala b/server/api/src/main/scala/org/cakesolutions/akkapatterns/api/tracking.scala
new file mode 100644
index 0000000..7d66c54
--- /dev/null
+++ b/server/api/src/main/scala/org/cakesolutions/akkapatterns/api/tracking.scala
@@ -0,0 +1,52 @@
+package org.cakesolutions.akkapatterns.api
+
+import java.util.{UUID, Date}
+import spray.json.DefaultJsonProtocol
+import org.cakesolutions.scalad.mongo.sprayjson.{SprayMongo, SprayMongoCollection, DateMarshalling, UuidMarshalling}
+import org.cakesolutions.akkapatterns.domain.Configured
+import com.mongodb.DB
+import spray.http._
+import spray.routing._
+import scala.concurrent.Future
+
+case class TrackingStat(path: String,
+ ip: Option[String],
+ auth: Option[UUID],
+ kind: String,
+ timestamp: Date = new Date,
+ id: UUID = UUID.randomUUID())
+
+trait TrackingFormats extends DefaultJsonProtocol
+ with UuidMarshalling with DateMarshalling {
+ protected implicit val TrackingStatFormat = jsonFormat6(TrackingStat)
+}
+
+trait TrackingMongo extends TrackingFormats with Configured {
+ protected implicit val EndpointHitStartProvider = new SprayMongoCollection[TrackingStat](configured[DB], "tracking")
+}
+
+trait Tracking extends TrackingMongo {
+ this: AuthenticationDirectives with HttpService =>
+
+ private val trackingMongo = new SprayMongo
+
+ def trackRequestT(request: HttpRequest): Any => Unit = {
+ val path = request.uri.split('?')(0) // not ideal for parameters in the path, e.g. uuids.
+ val ip = request.headers.find(_.name == "Remote-Address").map { _.value }
+ val auth = getToken(request)
+ val stat = TrackingStat(path, ip, auth, "request")
+
+ // the HttpService dispatcher is used to execute these inserts
+ Future{trackingMongo.insertFast(stat)}
+
+ // the code is executed when called, so the date is calculated when the response is ready
+ (r:Any) => (Future{trackingMongo.insertFast(stat.copy(kind = "response", timestamp = new Date))})
+ }
+
+ def trackRequestResponse: Directive0 = {
+ mapRequestContext { ctx =>
+ val logResponse = trackRequestT(ctx.request)
+ ctx.mapRouteResponse { response => logResponse(response); response}
+ }
+ }
+}
\ No newline at end of file
diff --git a/server/api/src/main/scala/org/cakesolutions/akkapatterns/api/user.scala b/server/api/src/main/scala/org/cakesolutions/akkapatterns/api/user.scala
new file mode 100644
index 0000000..31c9f13
--- /dev/null
+++ b/server/api/src/main/scala/org/cakesolutions/akkapatterns/api/user.scala
@@ -0,0 +1,29 @@
+package org.cakesolutions.akkapatterns.api
+
+import org.cakesolutions.akkapatterns.domain.User
+import akka.util.Timeout
+import spray.routing.HttpService
+import akka.actor.ActorRef
+import org.cakesolutions.akkapatterns.core.{NotRegisteredUser, RegisteredUser}
+
+
+trait UserService extends HttpService {
+ this: EndpointMarshalling with AuthenticationDirectives =>
+
+ import akka.pattern.ask
+ implicit val timeout: Timeout
+ def userActor: ActorRef
+
+ // will return code 666 if NotRegisteredUser is received
+ implicit val UserRegistrationErrorMarshaller = errorSelectingEitherMarshaller[NotRegisteredUser, RegisteredUser](666)
+
+ val userRoute =
+ path("user" / "register") {
+ post {
+ handleWith {user: User =>
+ (userActor ? RegisteredUser(user)).mapTo[Either[NotRegisteredUser, RegisteredUser]]
+ }
+ }
+ }
+
+}
diff --git a/server/api/src/main/scala/org/cakesolutions/akkapatterns/web/boot.scala b/server/api/src/main/scala/org/cakesolutions/akkapatterns/web/boot.scala
new file mode 100644
index 0000000..52e8616
--- /dev/null
+++ b/server/api/src/main/scala/org/cakesolutions/akkapatterns/web/boot.scala
@@ -0,0 +1,17 @@
+package org.cakesolutions.akkapatterns.web
+
+import org.cakesolutions.akkapatterns.core.ServerCore
+import org.cakesolutions.akkapatterns.api.Api
+import spray.can.server.SprayCanHttpServerApp
+import akka.actor.Props
+
+trait Web extends SprayCanHttpServerApp {
+ this: ServerCore =>
+
+ override lazy val system = actorSystem
+
+ val service = system.actorOf(Props[Api], "api")
+
+ newHttpServer(service, name = "spray-http-server") ! Bind("0.0.0.0", 8080)
+
+}
diff --git a/server/api/src/test/scala/org/cakesolutions/akkapatterns/api/CustomerServiceSpec.scala b/server/api/src/test/scala/org/cakesolutions/akkapatterns/api/CustomerServiceSpec.scala
new file mode 100644
index 0000000..5228dd1
--- /dev/null
+++ b/server/api/src/test/scala/org/cakesolutions/akkapatterns/api/CustomerServiceSpec.scala
@@ -0,0 +1,45 @@
+package org.cakesolutions.akkapatterns.api
+
+import spray.http.StatusCodes._
+import org.cakesolutions.akkapatterns.TestCustomerData
+import org.cakesolutions.akkapatterns.core.Neo4JFixtures
+import org.cakesolutions.akkapatterns.MongoCollectionFixture.Fix
+import java.util.UUID
+
+
+class CustomerServiceSpec extends ApiSpecs with CustomerService with TestCustomerData with Neo4JFixtures {
+
+ implicit val route = handled(customerRoute)
+
+ "/customers" should {
+ "fail to GET an invalid customer" in {
+ suppressNextException
+ Get("/customers/invalid").failsWith(NotFound)
+ }
+
+ "GET a valid customer" in new Fix("customers") {
+ val jan = customerController.get(TestCustomerJanId)
+ Get(s"/customers/${jan.id}").returns(jan)
+ }
+
+ "require a valid token to POST" in new Fix("customers") {
+ val jan = customerController.get(TestCustomerJanId)
+ val update = jan.copy(lastName = "changed")
+ suppressNextException
+ Post(s"/customers/${jan.id}", update).failsWith(MethodNotAllowed)
+ }
+
+ "update with a valid POST" in new Fix("customers") {
+ val jan = customerController.get(TestCustomerJanId)
+ val update = jan.copy(lastName = "changed")
+
+ {
+ implicit val auth = Token(UUID.randomUUID()) // need correct token
+ AuthPost(s"/customers/${jan.id}", update).returns(update)
+ }
+
+ Get(s"/customers/${jan.id}").returns(update)
+ }.pendingUntilFixed("@janm399 needs to setup a test data fixture for tokens")
+
+ }
+}
diff --git a/server/api/src/test/scala/org/cakesolutions/akkapatterns/api/HomeServiceSpecs.scala b/server/api/src/test/scala/org/cakesolutions/akkapatterns/api/HomeServiceSpecs.scala
new file mode 100644
index 0000000..cbcc013
--- /dev/null
+++ b/server/api/src/test/scala/org/cakesolutions/akkapatterns/api/HomeServiceSpecs.scala
@@ -0,0 +1,13 @@
+package org.cakesolutions.akkapatterns.api
+
+class HomeServiceSpecs extends ApiSpecs with HomeService {
+
+ implicit val route = homeRoute
+
+ "/" should {
+ "return the service info" in {
+ Get("/").returnsA[SystemInfo]
+ }
+ }
+
+}
diff --git a/server/api/src/test/scala/org/cakesolutions/akkapatterns/api/support.scala b/server/api/src/test/scala/org/cakesolutions/akkapatterns/api/support.scala
new file mode 100644
index 0000000..f004dc3
--- /dev/null
+++ b/server/api/src/test/scala/org/cakesolutions/akkapatterns/api/support.scala
@@ -0,0 +1,167 @@
+package org.cakesolutions.akkapatterns.api
+
+import java.util.UUID
+import spray.testkit.Specs2RouteTest
+import spray.http.{StatusCode, StatusCodes, HttpRequest}
+import spray.http.HttpHeaders.RawHeader
+import spray.httpx.RequestBuilding
+import spray.httpx.marshalling.Marshaller
+import spray.httpx.unmarshalling.Unmarshaller
+import spray.routing._
+import org.specs2.mutable.Specification
+import akka.contrib.jul.JavaLogging
+import spray.routing.{HttpService, Rejection}
+import scala.reflect.ClassTag
+import org.cakesolutions.akkapatterns.{NoActorSpecs, CleanMongo, ActorSpecs}
+import org.cakesolutions.akkapatterns.core.{ServerCore, CoreActorRefs}
+import akka.actor.ActorSystem
+import scala.concurrent.duration.Duration
+import java.util.concurrent.TimeUnit
+import akka.util.Timeout
+import akka.testkit.TestKit
+import org.specs2.specification.{Step, Fragments}
+import spray.util.LoggingContext
+import spray.http.StatusCodes._
+import org.cakesolutions.akkapatterns.domain.Configured
+
+case class Token(token: UUID)
+
+/** Provides default and easy authentication in testkit specs
+ * on endpoints that use [[org.cakesolutions.akkapatterns.api.AuthenticationDirectives]]
+ * for authentication.
+ *
+ * implicit AuthenticationToken doesn't work because of clashes with Spray :-(
+ */
+trait AuthenticatedTestkit {
+ this: Specs2RouteTest =>
+
+ private def addAuth(request: HttpRequest)(implicit token: Token) = {
+ // AuthenticationDirectives expects x-token in the header, even for GET / DELETE
+ request.copy(headers = RawHeader("x-token", token.token.toString) :: request.headers)
+ }
+
+ def AuthGet(uri: String)(implicit token: Token) = {
+ addAuth(RequestBuilding.Get(uri))
+ }
+
+ def AuthPost[T](uri: String, content: T)
+ (implicit token: Token,
+ marshaller: Marshaller[T]) = {
+ addAuth(RequestBuilding.Post(uri, content))
+ }
+
+ def AuthPut[T](uri: String, content: T)
+ (implicit token: Token,
+ marshaller: Marshaller[T]) = {
+ addAuth(RequestBuilding.Put(uri, content))
+ }
+
+ def AuthDelete(uri: String)(implicit token: Token) = {
+ // AuthenticationDirectives expects x-token in the header, not the query
+ addAuth(RequestBuilding.Delete(uri))
+ }
+
+}
+
+// e.g.
+// Get("/").succeeds()
+// Get("/").returns(myExample)
+// val get = Get("/").receive[MyObject]
+trait RouteTestNiceness {
+ this: Specs2RouteTest with Specification with JavaLogging =>
+
+ implicit val routeTestTimeout = RouteTestTimeout(Duration(5, TimeUnit.SECONDS))
+
+ implicit class ImplicitRouteCheck(request: HttpRequest)
+ (implicit route: Route, timeout: RouteTestTimeout) {
+
+ // `specs` works, but sometimes IntelliJ thinks there are problems
+ def specs[T](f: => T) {request ~> route ~> check(f)}
+
+ def receive[T](implicit um: Unmarshaller[T]): T = request ~> route ~> check {
+ try entityAs[T]
+ catch {
+ case up: Exception =>
+ if (up.getMessage != null && up.getMessage.startsWith("MalformedContent"))
+ failure(s"didn't get the response type we expected: ${response.entity}")
+ else throw up
+ }
+ }
+
+ // failures here might say application/json was unexpected. That probably means the
+ // exception handler kicked in and gave us a failure message instead of a T.
+ def returns[T](expected: T)(implicit um: Unmarshaller[T]) {
+ receive[T] === expected
+ }
+
+ def returnsA[T](implicit um: Unmarshaller[T]) = {
+ receive[T]
+ success
+ }
+
+ def succeeds() {specs(status === StatusCodes.OK)}
+
+ def succeedsWith(code: StatusCode) = specs {status === code}
+
+ // note, the failure type may be different in production:
+ // we use a different exception handler in testing.
+ def failsWith(code: StatusCode) = succeedsWith(code)
+
+ def rejectedAs[T <: Rejection: ClassTag] = specs {rejection must beAnInstanceOf[T]}
+ }
+}
+
+
+trait TestFailureHandling {
+ this: HttpService with Tracking =>
+
+ def handled(route: Route) = handleRejections(testRejectionHandler)(handleExceptions(testExceptionHandler)(trackRequestResponse(route)))
+
+ @volatile private var suppressed = false
+
+ def suppressNextException() {
+ suppressed = true
+ }
+
+ def testRejectionHandler: RejectionHandler = RejectionHandler.Default
+
+ def testExceptionHandler(implicit log: LoggingContext) = ExceptionHandler.fromPF {
+ case t: Throwable => ctx =>
+ // this ensures we see any logs that would otherwise be swallowed
+ // AND allows us to suppress expected exceptions with the "suppressException"
+ // mutable operation
+ if (!suppressed)
+ log.error(t, ctx.request.toString())
+ else
+ suppressed = false
+ ctx.complete(InternalServerError, t.toString)
+ }
+}
+
+
+/** Common imports for our Specs2RouteTest and fires up our Core
+ * Actor system. This way we can unit test the routes, but integrate
+ * test with Core.
+ */
+trait ApiSpecs extends NoActorSpecs
+with Specs2RouteTest
+with RouteTestNiceness
+with EndpointMarshalling
+with JavaLogging
+with DefaultAuthenticationDirectives
+with AuthenticatedTestkit
+with TestFailureHandling
+with ServerCore with CoreActorRefs with CleanMongo with Tracking {
+ this: HttpService =>
+
+ def actorSystem = system // for ServerCore
+ def actorRefFactory = system // for Specs2RouteTest
+
+
+ //implicit val auth = Token(... some test data here ...)
+
+ // we have to duplicate code here from ActorSpecs because Specs2RouteTest is INCOMPATIBLE with Testkit (go figure)
+ sequential
+ implicit val timeout = Timeout(10000)
+ override def map(fs: => Fragments) = super.map(fs) ^ Step(system.shutdown())
+}
diff --git a/sbt/src/main/resources/org/cakesolutions/akkapatterns/core/reporting/broken.jrxml b/server/core/src/main/resources/org/cakesolutions/akkapatterns/core/reporting/broken.jrxml
similarity index 100%
rename from sbt/src/main/resources/org/cakesolutions/akkapatterns/core/reporting/broken.jrxml
rename to server/core/src/main/resources/org/cakesolutions/akkapatterns/core/reporting/broken.jrxml
diff --git a/sbt/src/main/resources/org/cakesolutions/akkapatterns/core/reporting/empty.jrxml b/server/core/src/main/resources/org/cakesolutions/akkapatterns/core/reporting/empty.jrxml
similarity index 100%
rename from sbt/src/main/resources/org/cakesolutions/akkapatterns/core/reporting/empty.jrxml
rename to server/core/src/main/resources/org/cakesolutions/akkapatterns/core/reporting/empty.jrxml
diff --git a/server/core/src/main/scala/org/cakesolutions/akkapatterns/core/CoreActorRefs.scala b/server/core/src/main/scala/org/cakesolutions/akkapatterns/core/CoreActorRefs.scala
new file mode 100644
index 0000000..7466bec
--- /dev/null
+++ b/server/core/src/main/scala/org/cakesolutions/akkapatterns/core/CoreActorRefs.scala
@@ -0,0 +1,14 @@
+package org.cakesolutions.akkapatterns.core
+
+import akka.actor.ActorSystem
+
+
+trait CoreActorRefs {
+
+ def system: ActorSystem
+
+ def applicationActor = system.actorFor("/user/application")
+ def userActor = system.actorFor("/user/application/user")
+ def loginActor = system.actorFor("/user/application/authentication/login")
+
+}
diff --git a/server/core/src/main/scala/org/cakesolutions/akkapatterns/core/application.scala b/server/core/src/main/scala/org/cakesolutions/akkapatterns/core/application.scala
new file mode 100644
index 0000000..24162cf
--- /dev/null
+++ b/server/core/src/main/scala/org/cakesolutions/akkapatterns/core/application.scala
@@ -0,0 +1,37 @@
+package org.cakesolutions.akkapatterns.core
+
+import akka.actor.{ActorLogging, Props, Actor}
+import akka.routing.FromConfig
+
+case class GetImplementation()
+case class Implementation(title: String, version: String, build: String)
+
+object ApplicationActor {
+ case class Start()
+ case class Stop()
+}
+
+class ApplicationActor extends Actor with ActorLogging {
+ import ApplicationActor._
+
+ def receive = {
+ case GetImplementation() =>
+ val title = "Akka-Patterns"
+ val version = "1.0"
+ val build = "1.0"
+
+ sender ! Implementation(title, version, build)
+
+ case Start() =>
+ val messageDelivery = context.actorOf(
+ Props[MessageDeliveryActor].withRouter(FromConfig()).withDispatcher("low-priority-dispatcher"),
+ "messageDelivery"
+ )
+ context.actorOf(Props(new UserActor(messageDelivery)).withRouter(FromConfig()), "user")
+
+
+ case Stop() =>
+ context.children.foreach(context.stop _)
+
+ }
+}
diff --git a/sbt/src/main/scala/org/cakesolutions/akkapatterns/core/authentication/account.scala b/server/core/src/main/scala/org/cakesolutions/akkapatterns/core/authentication/account.scala
similarity index 100%
rename from sbt/src/main/scala/org/cakesolutions/akkapatterns/core/authentication/account.scala
rename to server/core/src/main/scala/org/cakesolutions/akkapatterns/core/authentication/account.scala
diff --git a/sbt/src/main/scala/org/cakesolutions/akkapatterns/core/authentication/authentication.scala b/server/core/src/main/scala/org/cakesolutions/akkapatterns/core/authentication/authentication.scala
similarity index 100%
rename from sbt/src/main/scala/org/cakesolutions/akkapatterns/core/authentication/authentication.scala
rename to server/core/src/main/scala/org/cakesolutions/akkapatterns/core/authentication/authentication.scala
diff --git a/sbt/src/main/scala/org/cakesolutions/akkapatterns/core/authentication/login.scala b/server/core/src/main/scala/org/cakesolutions/akkapatterns/core/authentication/login.scala
similarity index 100%
rename from sbt/src/main/scala/org/cakesolutions/akkapatterns/core/authentication/login.scala
rename to server/core/src/main/scala/org/cakesolutions/akkapatterns/core/authentication/login.scala
diff --git a/sbt/src/main/scala/org/cakesolutions/akkapatterns/core/boot.scala b/server/core/src/main/scala/org/cakesolutions/akkapatterns/core/boot.scala
similarity index 62%
rename from sbt/src/main/scala/org/cakesolutions/akkapatterns/core/boot.scala
rename to server/core/src/main/scala/org/cakesolutions/akkapatterns/core/boot.scala
index 5a8a063..5fedc35 100644
--- a/sbt/src/main/scala/org/cakesolutions/akkapatterns/core/boot.scala
+++ b/server/core/src/main/scala/org/cakesolutions/akkapatterns/core/boot.scala
@@ -4,23 +4,18 @@ import akka.actor.{Props, ActorSystem}
import akka.pattern.ask
import akka.util.Timeout
import concurrent.Await
+import org.cakesolutions.akkapatterns.core.ApplicationActor.Start
-case class Start()
-
-case object InmatesAreRunningTheAsylum
-case class Started()
-
-case class Stop()
trait ServerCore {
implicit def actorSystem: ActorSystem
- implicit val timeout = Timeout(30000)
+ implicit val timeout: Timeout
val application = actorSystem.actorOf(
props = Props[ApplicationActor],
name = "application"
)
- Await.ready(application ? Start(), timeout.duration)
+ application ! Start()
}
diff --git a/server/core/src/main/scala/org/cakesolutions/akkapatterns/core/customer.scala b/server/core/src/main/scala/org/cakesolutions/akkapatterns/core/customer.scala
new file mode 100644
index 0000000..c029172
--- /dev/null
+++ b/server/core/src/main/scala/org/cakesolutions/akkapatterns/core/customer.scala
@@ -0,0 +1,53 @@
+package org.cakesolutions.akkapatterns.core
+
+import org.cakesolutions.akkapatterns.domain._
+import org.cakesolutions.scalad.mongo.sprayjson._
+
+/**
+ * An alternative to using Actors (see UserActor) is to have traditional controllers.
+ *
+ * The advantage is clear: much simpler code that remains typesafe vs akka.ask.
+ *
+ * The disadvantage is that controllers cannot be distributed as easily as actors.
+ * However, it is possible to refactor the internals of a controller to use an
+ * Actor implementation if that is needed. And fire and forget tasks that are done as
+ * part of a controller are most certainly prime candidates as Actor actions.
+ */
+class CustomerController extends CustomerMongo with Configured {
+
+ val mongo = new SprayMongo
+
+ /**
+ * Registers a customer and a user. After registering, we have a user account for the given customer.
+ *
+ * @param customer the customer
+ * @param user the user
+ */
+ def register(customer: Customer, user: User) {
+ mongo.insert(customer)
+ ??? // still need to register the user in neo4j... I don't really get the whole user/customer distinction...
+ }
+
+ // it is better to unwrap the Option here, as Option[T] endpoints are awful ... it
+ // is much better to catch NoElementExceptions in the FailureHandler and return an
+ // appropriately formatted status response.
+ def get(id: CustomerReference) = mongo.findOne[Customer]("id" :> id).get
+
+ /**
+ * @param userDetail the user making the call
+ * @param customer the customer to be updated
+ */
+ def update(userDetail: UserDetailT[CustomerUserKind], customer: Customer) = userDetail.kind match {
+ // this api design means that the admin user can't update customers... intentional?
+ case CustomerUserKind(`customer`.id) =>
+ mongo.findAndReplace("id" :> customer.id, customer)
+ customer
+
+ // this defensive coding may be unnecessary due to the way we are always called from the API
+ // to avoid this form of work duplication, ensure that your codebase has a clear and well documented
+ // policy for authentication and access checks. Indeed, the UserDetailT should not be passed around
+ // if authentication checks have already been performed.
+ case _ => throw new IllegalArgumentException(s"${userDetail.userReference} does not have access rights to any customers.")
+ }
+
+}
diff --git a/sbt/src/main/scala/org/cakesolutions/akkapatterns/core/messagedelivery.scala b/server/core/src/main/scala/org/cakesolutions/akkapatterns/core/messagedelivery.scala
similarity index 93%
rename from sbt/src/main/scala/org/cakesolutions/akkapatterns/core/messagedelivery.scala
rename to server/core/src/main/scala/org/cakesolutions/akkapatterns/core/messagedelivery.scala
index 4fd933f..c956221 100644
--- a/sbt/src/main/scala/org/cakesolutions/akkapatterns/core/messagedelivery.scala
+++ b/server/core/src/main/scala/org/cakesolutions/akkapatterns/core/messagedelivery.scala
@@ -1,15 +1,15 @@
package org.cakesolutions.akkapatterns.core
-import spray.http.HttpRequest
+import spray.http.{HttpResponse, HttpRequest}
import com.typesafe.config.Config
import org.cakesolutions.akkapatterns.domain._
-import spray.client.pipelining._
import java.util.Properties
import akka.actor.Actor
-import org.cakesolutions.akkapatterns.{ActorHttpIO, HttpIO}
+import org.cakesolutions.akkapatterns.Nexmo
import javax.mail.internet.{MimeMessage, InternetAddress}
import javax.mail._
import scala.Some
+import scala.concurrent.Future
/**
* The address where the message should be delivered to. We currently support emailing the message or sending it
@@ -42,7 +42,9 @@ case class DeliverActivationCode(address: DeliveryAddress, userReference: UserRe
* ``IOBridge`` and other Spray components.
*/
trait NexmoTextMessageDelivery {
- this: HttpIO =>
+ this: Actor =>
+
+ import context.dispatcher
/**
* Returns the API key for Nexmo.
@@ -56,10 +58,6 @@ trait NexmoTextMessageDelivery {
*/
def apiSecret: String
- import scala.concurrent.ExecutionContext.Implicits.global
-
- private val pipeline = sendReceive(httpClient) // makeConduit("rest.nexmo.com"))
-
/**
* Delivers the text message ``secret`` to the phone number ``mobileNumber``. The ``mobileNumber`` needs to be in
* full international format, without spaces, but without the leading "+", for example ``4477712345678`` for
@@ -72,7 +70,7 @@ trait NexmoTextMessageDelivery {
// http://rest.nexmo.com/sms/json?api_key=3e08b948&api_secret=584f23de&from=Cake&to=*********&text=Hello
val url = "/sms/json?api_key=%s&api_secret=%s&from=Zoetic&to=%s&text=%s" format (apiKey, apiSecret, mobileNumber, secret)
val request = HttpRequest(spray.http.HttpMethods.POST, url)
- pipeline(request) onSuccess {
+ Nexmo.sendReceive(context.system)(request) onSuccess {
case response =>
// Sort out the response. Maybe bang to health agent if we're out of credits or some such
}
@@ -167,7 +165,7 @@ trait SimpleEmailMessageDelivery {
/**
* Delivers the secret to the address
*/
-class MessageDeliveryActor extends Actor with ActorHttpIO with NexmoTextMessageDelivery with SimpleEmailMessageDelivery
+class MessageDeliveryActor extends Actor with NexmoTextMessageDelivery with SimpleEmailMessageDelivery
with ConfigEmailConfiguration {
def config = context.system.settings.config
diff --git a/sbt/src/main/scala/org/cakesolutions/akkapatterns/core/neo4j.scala b/server/core/src/main/scala/org/cakesolutions/akkapatterns/core/neo4j.scala
similarity index 100%
rename from sbt/src/main/scala/org/cakesolutions/akkapatterns/core/neo4j.scala
rename to server/core/src/main/scala/org/cakesolutions/akkapatterns/core/neo4j.scala
diff --git a/sbt/src/main/scala/org/cakesolutions/akkapatterns/core/reporting/datasource.scala b/server/core/src/main/scala/org/cakesolutions/akkapatterns/core/reporting/datasource.scala
similarity index 100%
rename from sbt/src/main/scala/org/cakesolutions/akkapatterns/core/reporting/datasource.scala
rename to server/core/src/main/scala/org/cakesolutions/akkapatterns/core/reporting/datasource.scala
diff --git a/sbt/src/main/scala/org/cakesolutions/akkapatterns/core/reporting/jasperreports.scala b/server/core/src/main/scala/org/cakesolutions/akkapatterns/core/reporting/jasperreports.scala
similarity index 100%
rename from sbt/src/main/scala/org/cakesolutions/akkapatterns/core/reporting/jasperreports.scala
rename to server/core/src/main/scala/org/cakesolutions/akkapatterns/core/reporting/jasperreports.scala
diff --git a/sbt/src/main/scala/org/cakesolutions/akkapatterns/core/reporting/package.scala b/server/core/src/main/scala/org/cakesolutions/akkapatterns/core/reporting/package.scala
similarity index 100%
rename from sbt/src/main/scala/org/cakesolutions/akkapatterns/core/reporting/package.scala
rename to server/core/src/main/scala/org/cakesolutions/akkapatterns/core/reporting/package.scala
diff --git a/sbt/src/main/scala/org/cakesolutions/akkapatterns/core/reporting/user.scala b/server/core/src/main/scala/org/cakesolutions/akkapatterns/core/reporting/user.scala
similarity index 100%
rename from sbt/src/main/scala/org/cakesolutions/akkapatterns/core/reporting/user.scala
rename to server/core/src/main/scala/org/cakesolutions/akkapatterns/core/reporting/user.scala
diff --git a/sbt/src/main/scala/org/cakesolutions/akkapatterns/core/user.scala b/server/core/src/main/scala/org/cakesolutions/akkapatterns/core/user.scala
similarity index 100%
rename from sbt/src/main/scala/org/cakesolutions/akkapatterns/core/user.scala
rename to server/core/src/main/scala/org/cakesolutions/akkapatterns/core/user.scala
diff --git a/server/core/src/main/scala/org/cakesolutions/akkapatterns/io.scala b/server/core/src/main/scala/org/cakesolutions/akkapatterns/io.scala
new file mode 100644
index 0000000..5c15c74
--- /dev/null
+++ b/server/core/src/main/scala/org/cakesolutions/akkapatterns/io.scala
@@ -0,0 +1,67 @@
+package org.cakesolutions.akkapatterns
+
+import akka.actor.{Props, Actor, ActorSystem}
+import spray.io.IOExtension
+import com.typesafe.config.ConfigFactory
+import com.github.sstone.amqp.ConnectionOwner
+import com.github.sstone.amqp.Amqp.ExchangeParameters
+import com.rabbitmq.client.ConnectionFactory
+import spray.can.client.HttpClient
+import akka.actor._
+import spray.client.HttpConduit
+import spray.can.client.DefaultHttpClient
+import akka.spray.ExtensionActorRef
+
+abstract class SendReceive(server: String, port: Int = 443, sslEnabled: Boolean = true) extends ExtensionId[ExtensionActorRef] {
+
+ def createExtension(system: ExtendedActorSystem) = {
+ val client = DefaultHttpClient(system)
+ val conduitName = "http-conduit-" + port + "-" +
+ (if (sslEnabled) "ssl" else "plain") +
+ "-" + server
+ val conduit = system.actorOf(
+ props = Props(
+ new HttpConduit(client, server, port = port, sslEnabled = sslEnabled)
+ ),
+ name = conduitName
+ )
+ new ExtensionActorRef(conduit)
+ }
+
+ def sendReceive(system: ActorSystem) = HttpConduit.sendReceive(get(system))
+
+}
+
+object Foursquare extends SendReceive("api.foursquare.com")
+object ITunes extends SendReceive("buy.itunes.apple.com")
+object ITunesSandbox extends SendReceive("sandbox.itunes.apple.com")
+object Facebook extends SendReceive("graph.facebook.com")
+object Twitter extends SendReceive("api.twitter.com")
+object Nexmo extends SendReceive("rest.nexmo.com")
+
+
+
+/**
+ * Provides connection & access to the AMQP broker
+ */
+trait AmqpIO {
+ implicit def actorSystem: ActorSystem
+
+ // prepare the AMQP connection factory
+ final lazy val connectionFactory = new ConnectionFactory(); connectionFactory.setHost("localhost")
+ // connect to the AMQP exchange
+ final lazy val amqpExchange = ExchangeParameters(name = "amq.direct", exchangeType = "", passive = true)
+
+ // create a "connection owner" actor, which will try and reconnect automatically if the connection ins lost
+ val connection = actorSystem.actorOf(Props(new ConnectionOwner(connectionFactory)))
+
+}
+
+/**
+ * Convenience ``AmqpIO`` implementation that can be mixed in to actors.
+ */
+trait ActorAmqpIO extends AmqpIO {
+ this: Actor =>
+ final implicit def actorSystem = context.system
+
+}
\ No newline at end of file
diff --git a/server/core/src/test/scala/org/cakesolutions/akkapatterns/core/CustomerSpecs.scala b/server/core/src/test/scala/org/cakesolutions/akkapatterns/core/CustomerSpecs.scala
new file mode 100644
index 0000000..8860b2d
--- /dev/null
+++ b/server/core/src/test/scala/org/cakesolutions/akkapatterns/core/CustomerSpecs.scala
@@ -0,0 +1,31 @@
+package org.cakesolutions.akkapatterns.core
+
+import org.cakesolutions.akkapatterns.{TestCustomerData, NoActorSpecs, CleanMongo, ActorSpecs}
+import org.cakesolutions.akkapatterns.domain.{CustomerUserKind, UserDetailT, CustomerMongo}
+import org.cakesolutions.akkapatterns.MongoCollectionFixture.Fix
+import java.util.UUID
+
+
+class CustomerSpecs extends NoActorSpecs with CleanMongo with CustomerMongo with TestCustomerData with Neo4JFixtures {
+
+ neo4jFixtures
+
+ val controller = new CustomerController
+
+ "Customer actor" should {
+ "be updateable" in new Fix("customers") {
+ val jan = controller.get(TestCustomerJanId)
+ val auth = UserDetailT(RootUser.id, CustomerUserKind(jan.id))
+
+ controller.update(auth, jan.copy(lastName = "changed"))
+ controller.get(TestCustomerJanId) !== jan
+ }
+
+ "not allow the wrong user to update" in new Fix("customers") {
+ val jan = controller.get(TestCustomerJanId)
+ val bad = UserDetailT(RootUser.id, CustomerUserKind(UUID.randomUUID()))
+
+ controller.update(bad, jan.copy(lastName = "changed")) must throwA[IllegalArgumentException]
+ }
+ }
+}
diff --git a/server/core/src/test/scala/org/cakesolutions/akkapatterns/core/UserActorSpec.scala b/server/core/src/test/scala/org/cakesolutions/akkapatterns/core/UserActorSpec.scala
new file mode 100644
index 0000000..37a68f6
--- /dev/null
+++ b/server/core/src/test/scala/org/cakesolutions/akkapatterns/core/UserActorSpec.scala
@@ -0,0 +1,23 @@
+package org.cakesolutions.akkapatterns.core
+
+import akka.testkit.TestActorRef
+import org.cakesolutions.akkapatterns.{MongoCollectionFixture, TestMongo, ActorSpecs}
+import org.cakesolutions.akkapatterns.domain.{SuperuserKind, User}
+import org.cakesolutions.akkapatterns.MongoCollectionFixture.Fix
+
+class UserActorSpec extends ActorSpecs with Neo4JFixtures {
+
+ neo4jFixtures
+
+ val actor = TestActorRef(new UserActor(testActor))
+
+ "Basic user operations" should {
+ "Find the root user" in {
+ actor ! GetUserByUsername("root")
+ expectMsgType[Option[User]] should beLike {
+ case Some(user) if user.kind == SuperuserKind => ok
+ }
+ }
+ }
+
+}
diff --git a/sbt/src/main/scala/org/cakesolutions/akkapatterns/core/sanity.scala b/server/core/src/test/scala/org/cakesolutions/akkapatterns/core/neo4jsupport.scala
similarity index 79%
rename from sbt/src/main/scala/org/cakesolutions/akkapatterns/core/sanity.scala
rename to server/core/src/test/scala/org/cakesolutions/akkapatterns/core/neo4jsupport.scala
index 85d4a16..3843dc6 100644
--- a/sbt/src/main/scala/org/cakesolutions/akkapatterns/core/sanity.scala
+++ b/server/core/src/test/scala/org/cakesolutions/akkapatterns/core/neo4jsupport.scala
@@ -3,11 +3,8 @@ package org.cakesolutions.akkapatterns.core
import org.cakesolutions.akkapatterns.domain.{SuperuserKind, User, UserFormats}
import java.util.UUID
-/**
- * Initial system sanity checks
- */
-trait SanityChecks extends TypedGraphDatabase with UserFormats with SprayJsonNodeMarshalling
- with UserGraphDatabaseIndexes {
+// TODO https://github.com/janm399/akka-patterns/issues/35
+trait Neo4JFixtures extends TypedGraphDatabase with UserFormats with SprayJsonNodeMarshalling with UserGraphDatabaseIndexes {
val RootUserPassword = "*******"
val RootUser = User(UUID.fromString("a3372060-2b3b-11e2-81c1-0800200c9a66"), "root", "", "janm@cakesolutions.net", None, "Jan", "Machacek", SuperuserKind).resetPassword(RootUserPassword)
@@ -27,8 +24,8 @@ trait SanityChecks extends TypedGraphDatabase with UserFormats with SprayJsonNod
}
}
- def ensureSanity: Boolean = synchronized {
+ def neo4jFixtures: Boolean = synchronized {
ensureUserSanity
}
-}
+}
\ No newline at end of file
diff --git a/sbt/src/test/scala/org/cakesolutions/akkapatterns/core/reporting/ReportRunnerSpec.scala b/server/core/src/test/scala/org/cakesolutions/akkapatterns/core/reporting/ReportRunnerSpec.scala
similarity index 84%
rename from sbt/src/test/scala/org/cakesolutions/akkapatterns/core/reporting/ReportRunnerSpec.scala
rename to server/core/src/test/scala/org/cakesolutions/akkapatterns/core/reporting/ReportRunnerSpec.scala
index c7c847e..d519545 100644
--- a/sbt/src/test/scala/org/cakesolutions/akkapatterns/core/reporting/ReportRunnerSpec.scala
+++ b/server/core/src/test/scala/org/cakesolutions/akkapatterns/core/reporting/ReportRunnerSpec.scala
@@ -2,13 +2,12 @@ package org.cakesolutions.akkapatterns.core.reporting
import org.specs2.mutable.Specification
import org.specs2.execute.Result
-import org.cakesolutions.akkapatterns.TestData
import java.io.FileOutputStream
/**
* @author janmachacek
*/
-class ReportRunnerSpec extends Specification with TestData with ReportFormats {
+class ReportRunnerSpec extends Specification with ReportFormats {
val runner = new ReportRunner with JRXmlReportCompiler with ClasspathResourceReportLoader
@@ -23,7 +22,10 @@ class ReportRunnerSpec extends Specification with TestData with ReportFormats {
}
"simple report" in {
- runReport("empty.jrxml", EmptyExpression, ProductListParameterExpression(Users.newUser("janm") :: Users.newUser("anirvanc") :: Nil))
+ runReport("empty.jrxml", EmptyExpression, ProductListParameterExpression(
+// Users.newUser("janm") :: Users.newUser("anirvanc") :: Nil
+ Nil
+ ))
}
def runReport(source: String, parametersExpression: Expression, dataSourceExpression: DataSourceExpression): Result = {
diff --git a/server/domain/src/main/resources/application.conf b/server/domain/src/main/resources/application.conf
new file mode 100644
index 0000000..b99a31f
--- /dev/null
+++ b/server/domain/src/main/resources/application.conf
@@ -0,0 +1,96 @@
+akka {
+ event-handlers = ["akka.contrib.jul.JavaLoggingEventHandler"]
+ loglevel = DEBUG
+ stdout-loglevel = WARNING
+ log-config-on-start = off
+
+ actor {
+ debug {
+# receive = on
+# autoreceive = on
+# lifecycle = on
+# fsm = on
+# event-stream = on
+ unhandled = on
+ }
+
+ deployment {
+ "/application/*" {
+ router = "round-robin"
+# nr-of-instances = 20
+ resizer {
+ lower-bound = 1
+ upper-bound = 40
+ }
+ }
+ }
+
+ default-dispatcher {
+ type = "Dispatcher"
+ executor = "fork-join-executor"
+ fork-join-executor {
+ parallelism-min = 4
+ parallelism-factor = 10
+ parallelism-max = 64
+ }
+ throughput = 10
+ }
+
+ }
+}
+
+low-priority-dispatcher {
+ type = "Dispatcher"
+ executor = "fork-join-executor"
+ fork-join-executor {
+ parallelism-min = 2
+ parallelism-factor = 2
+ parallelism-max = 8
+ }
+ throughput = 1
+}
+
+
+test {
+ db {
+ mongo {
+ name: "patterns-test"
+ connections: 10
+ concern: ACKNOWLEDGED
+ hosts {
+ "localhost": 27017
+ }
+ }
+ cassandra {
+ cluster: "patterns-test"
+ connections: 10
+ hosts {
+ "localhost": 9160
+ }
+ }
+ }
+}
+
+main {
+ db {
+ mongo {
+ name: "patterns"
+ connections: 50
+ concern: ACKNOWLEDGED
+ hosts {
+ "mongodb.host.1": 27017
+ "mongodb.host.2": 27017
+ "mongodb.host.3": 27017
+ }
+ }
+ cassandra {
+ cluster: "patterns"
+ connections: 50
+ hosts {
+ "cassandra.host.1": 9160
+ "cassandra.host.2": 9160
+ "cassandra.host.3": 9160
+ }
+ }
+ }
+}
diff --git a/sbt/src/main/scala/org/cakesolutions/akkapatterns/domain/configuration.scala b/server/domain/src/main/scala/org/cakesolutions/akkapatterns/domain/configuration.scala
similarity index 65%
rename from sbt/src/main/scala/org/cakesolutions/akkapatterns/domain/configuration.scala
rename to server/domain/src/main/scala/org/cakesolutions/akkapatterns/domain/configuration.scala
index b4dfb17..94fd559 100644
--- a/sbt/src/main/scala/org/cakesolutions/akkapatterns/domain/configuration.scala
+++ b/server/domain/src/main/scala/org/cakesolutions/akkapatterns/domain/configuration.scala
@@ -2,6 +2,9 @@ package org.cakesolutions.akkapatterns.domain
import collection.mutable
import reflect.{ClassTag, classTag}
+import io.{Codec, Source}
+import org.springframework.core.io.DefaultResourceLoader
+import java.io.InputStream
/**
* Stores the configuration
@@ -44,4 +47,20 @@ trait Configuration {
a
}
-}
\ No newline at end of file
+}
+
+trait Resources {
+
+ protected def readResource(resource: String) = new DefaultResourceLoader().getResource(resource).getInputStream
+
+ protected implicit class StreamString(stream: InputStream) {
+ @deprecated("use Scala IO", "HEAD")
+ def mkString =
+ try Source.fromInputStream(stream)(Codec.UTF8).mkString
+ finally stream.close()
+ }
+
+// PathMatchingResourcePatternResolver
+
+}
+
diff --git a/server/domain/src/main/scala/org/cakesolutions/akkapatterns/domain/customer.scala b/server/domain/src/main/scala/org/cakesolutions/akkapatterns/domain/customer.scala
new file mode 100644
index 0000000..3fd6871
--- /dev/null
+++ b/server/domain/src/main/scala/org/cakesolutions/akkapatterns/domain/customer.scala
@@ -0,0 +1,25 @@
+package org.cakesolutions.akkapatterns.domain
+
+import spray.json.DefaultJsonProtocol
+import org.cakesolutions.scalad.mongo.sprayjson.{SprayMongoCollection, UuidMarshalling}
+import com.mongodb.DB
+
+case class Customer(id: CustomerReference,
+ firstName: String, lastName: String,
+ email: String, addresses: Seq[Address])
+
+case class Address(line1: String, line2: String, line3: String)
+
+trait CustomerFormats extends DefaultJsonProtocol with UuidMarshalling {
+
+ implicit val AddressFormat = jsonFormat3(Address)
+ implicit val CustomerFormat = jsonFormat5(Customer)
+
+}
+
+trait CustomerMongo extends CustomerFormats {
+ this: Configured =>
+ import org.cakesolutions.scalad.mongo.sprayjson._
+
+ protected implicit val CustomerProvider = new SprayMongoCollection[Customer](configured[DB], "customers", "id":>1)
+}
\ No newline at end of file
diff --git a/sbt/src/main/scala/org/cakesolutions/akkapatterns/domain/domain.scala b/server/domain/src/main/scala/org/cakesolutions/akkapatterns/domain/domain.scala
similarity index 61%
rename from sbt/src/main/scala/org/cakesolutions/akkapatterns/domain/domain.scala
rename to server/domain/src/main/scala/org/cakesolutions/akkapatterns/domain/domain.scala
index a4f86f4..7634f30 100644
--- a/sbt/src/main/scala/org/cakesolutions/akkapatterns/domain/domain.scala
+++ b/server/domain/src/main/scala/org/cakesolutions/akkapatterns/domain/domain.scala
@@ -16,6 +16,11 @@ package object domain {
/**
* Type alias for customer identity
+ *
+ * NOTE: consider the alternative, using a `case class CustomerReference(id: UUID)`
+ * which is slightly more verbose but ensures type safety throughout the code.
+ * If your code has lots of UUIDs, you'll be *really* glad of type safe ids,
+ * trust me!
*/
type CustomerReference = UUID
diff --git a/server/domain/src/main/scala/org/cakesolutions/akkapatterns/domain/nosql.scala b/server/domain/src/main/scala/org/cakesolutions/akkapatterns/domain/nosql.scala
new file mode 100644
index 0000000..9f46e4c
--- /dev/null
+++ b/server/domain/src/main/scala/org/cakesolutions/akkapatterns/domain/nosql.scala
@@ -0,0 +1,43 @@
+package org.cakesolutions.akkapatterns.domain
+
+import _root_.me.prettyprint.hector.api.{HConsistencyLevel, ConsistencyLevelPolicy}
+import com.mongodb.MongoOptions
+import _root_.me.prettyprint.cassandra.service.{OperationType, CassandraHostConfigurator}
+import me.prettyprint.hector.api.factory.HFactory
+import Settings.{Mongo, Cassandra}
+import scala.collection.JavaConversions._
+
+trait NoSqlConfig {
+
+ protected def cassandra(config: Cassandra) = {
+ val configurator = new CassandraHostConfigurator()
+ configurator.setHosts(config.hosts)
+ configurator.setMaxActive(config.connections)
+ HFactory.getOrCreateCluster(config.cluster, configurator)
+ }
+
+
+ protected def mongo(config: Mongo) = {
+ val options = new MongoOptions
+ options.connectionsPerHost = config.connections
+ val m = new com.mongodb.Mongo(config.hosts, options)
+ m.setWriteConcern(config.concern)
+ m.getDB(config.name)
+ }
+}
+
+object ConsistencyPolicy extends ConsistencyLevelPolicy {
+ def get(op: OperationType): HConsistencyLevel = {
+ HConsistencyLevel.LOCAL_QUORUM
+ }
+
+ def get(op: OperationType, cfName: String): HConsistencyLevel = {
+ (op, cfName) match {
+ case (OperationType.READ, "countIndex") => HConsistencyLevel.ONE
+ case (OperationType.READ, "count") => HConsistencyLevel.ONE
+ case (OperationType.WRITE, "countIndex") => HConsistencyLevel.LOCAL_QUORUM
+ case (OperationType.WRITE, "count") => HConsistencyLevel.LOCAL_QUORUM
+ case _ => HConsistencyLevel.LOCAL_QUORUM
+ }
+ }
+}
\ No newline at end of file
diff --git a/server/domain/src/main/scala/org/cakesolutions/akkapatterns/domain/settings.scala b/server/domain/src/main/scala/org/cakesolutions/akkapatterns/domain/settings.scala
new file mode 100644
index 0000000..3932585
--- /dev/null
+++ b/server/domain/src/main/scala/org/cakesolutions/akkapatterns/domain/settings.scala
@@ -0,0 +1,65 @@
+package org.cakesolutions.akkapatterns.domain
+
+import com.typesafe.config.ConfigFactory
+import com.mongodb.{ServerAddress, WriteConcern}
+import scala.collection.JavaConversions._
+import akka.contrib.jul.JavaLogging
+
+object Settings extends JavaLogging {
+
+ // https://groups.google.com/d/topic/scala-user/wzguzEJtLaI/discussion
+ private val overrides = ConfigFactory.load("local")
+ private val config = overrides.withFallback(ConfigFactory.load())
+
+ private def unmerged(path: String) =
+ if (overrides.hasPath(path)) overrides.getConfig(path)
+ else config.getConfig(path)
+
+ case class Cassandra(cluster: String, connections: Int, hosts: String)
+ object Cassandra {
+ def apply(base: String) = {
+ val c = config.getConfig(base)
+ val cluster = c.getString("cluster")
+ val connections = c.getInt("connections")
+ val hosts = unmerged(base + ".hosts").entrySet().map{e =>
+ e.getKey.replaceAll("\"", "") + ":" + e.getValue.unwrapped()
+ }.mkString(",")
+ new Cassandra(cluster, connections, hosts)
+ }
+ }
+
+ case class Mongo(name: String, connections: Int, hosts: List[ServerAddress], concern: WriteConcern)
+ object Mongo {
+ def apply(base: String) = {
+ val c = config.getConfig(base)
+ val name = c.getString("name")
+ val connections = c.getInt("connections")
+ val concern = WriteConcern.valueOf(c.getString("concern"))
+ val hosts = unmerged(base + ".hosts").entrySet().map{e =>
+ new ServerAddress(e.getKey.replaceAll("\"", ""), e.getValue.unwrapped().asInstanceOf[Integer])
+ }.toList
+ new Mongo(name, connections, hosts, concern)
+ }
+ }
+
+ case class Db(cassandra: Cassandra, mongo: Mongo)
+ case class Main(db: Db)
+
+ val main = try Main(
+ Db(
+ Cassandra("main.db.cassandra"),
+ Mongo("main.db.mongo")
+ )
+ ) catch {
+ case t: Throwable => log.error(t, "Settings.main") ; throw t
+ }
+
+ val test = try Main(
+ Db(
+ Cassandra("test.db.cassandra"),
+ Mongo("test.db.mongo")
+ )
+ ) catch {
+ case t: Throwable => log.error(t, "Settings.test") ; throw t
+ }
+}
diff --git a/sbt/src/main/scala/org/cakesolutions/akkapatterns/domain/user.scala b/server/domain/src/main/scala/org/cakesolutions/akkapatterns/domain/user.scala
similarity index 72%
rename from sbt/src/main/scala/org/cakesolutions/akkapatterns/domain/user.scala
rename to server/domain/src/main/scala/org/cakesolutions/akkapatterns/domain/user.scala
index 997dbb0..3381378 100644
--- a/sbt/src/main/scala/org/cakesolutions/akkapatterns/domain/user.scala
+++ b/server/domain/src/main/scala/org/cakesolutions/akkapatterns/domain/user.scala
@@ -1,10 +1,11 @@
package org.cakesolutions.akkapatterns.domain
import spray.json._
-import org.cakesolutions.akkapatterns.UuidFormats
+import com.mongodb.DB
+import org.cakesolutions.scalad.mongo.sprayjson.UuidMarshalling
/**
- * The user record, which stores the identtiy, the username and the password
+ * The user record, which stores the identity, the username and the password
*
* @author janmachacek
*/
@@ -29,7 +30,7 @@ case class CustomerUserKind(customerReference: CustomerReference) extends UserKi
case object GuestUserKind extends UserKind
/**
- * The user detail about an authenticated user. It contains the user ``id`` and the detailm which further refines
+ * The user detail about an authenticated user. It contains the user ``id`` and the details which further refines
* the kind of user we're dealing with.
*
* @param userReference the identity of the authenticated user
@@ -38,12 +39,13 @@ case object GuestUserKind extends UserKind
*/
case class UserDetailT[A <: UserKind](userReference: UserReference, kind: A)
-/**
- * Trait that contains the [[spray.json.JsonFormat]] instances for the user
- * management
- */
-trait UserFormats extends DefaultJsonProtocol with UuidFormats {
+// Spray JSON marshalling for the User hierarchy
+trait UserFormats extends DefaultJsonProtocol with UuidMarshalling {
+
+ // the penalty we pay for a type hierarchy is an overly complex
+ // marshalling format. It is often worth considering a data
+ // model that does not enforce a hierarchy.
implicit object UserKindFormat extends JsonFormat[UserKind] {
private val Superuser = JsString("superuser")
private val Customer = JsString("customer")
@@ -70,4 +72,12 @@ trait UserFormats extends DefaultJsonProtocol with UuidFormats {
implicit val UserFormat = jsonFormat8(User)
-}
\ No newline at end of file
+}
+
+// this is how we would use scalad for the user database, but we're actually using Neo4J
+//trait UserMongo extends UserFormats {
+// this: Configured =>
+// import org.cakesolutions.scalad.mongo.sprayjson._
+//
+// protected implicit val UserProvider = new SprayMongoCollection[User](configured[DB], "users", "id":>1, "username":> 1)
+//}
\ No newline at end of file
diff --git a/server/domain/src/main/xsd/PurchaseOrder.xsd b/server/domain/src/main/xsd/PurchaseOrder.xsd
new file mode 100644
index 0000000..b393dab
--- /dev/null
+++ b/server/domain/src/main/xsd/PurchaseOrder.xsd
@@ -0,0 +1,66 @@
+
+
+
+
+ Purchase order schema for Example.com.
+ Copyright 2000 Example.com. All rights reserved.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/server/logging.properties b/server/logging.properties
new file mode 100644
index 0000000..8a0c5df
--- /dev/null
+++ b/server/logging.properties
@@ -0,0 +1,29 @@
+# to enable this file, you must pass the following parameters to the JVM
+#
+# -Djava.util.logging.config.file=logging.properties
+#
+# It should be obvious in IntelliJ's Run settings how to do this
+# (might need to add ../ if running from a module).
+#
+# Maven is tricky and it reported that setting MAVEN_OPTS will work.
+#
+# http://stackoverflow.com/questions/14269492
+#
+# In SBT should work with our build.scala.
+
+handlers = java.util.logging.ConsoleHandler
+
+java.util.logging.ConsoleHandler.level = ALL
+java.util.logging.ConsoleHandler.filter = com.github.fommil.logging.ClassnameFilter
+java.util.logging.ConsoleHandler.formatter = com.github.fommil.logging.CustomFormatter
+com.github.fommil.logging.CustomFormatter.format = %L: %m [%c] (%n) %e %E %S
+
+com.github.fommil.logging.CustomFormatter.stackExclude = \
+ spray. scala. akka. com.mongodb. org.specs2. scalaz. \
+ org.jetbrains. com.intellij java. sun. com.sun
+
+
+akka.level = CONFIG
+org.cakesolutions.level = INFO
+
+.level = WARNING
diff --git a/sbt/src/main/resources/madouse.jpg b/server/main/src/main/resources/madouse.jpg
similarity index 100%
rename from sbt/src/main/resources/madouse.jpg
rename to server/main/src/main/resources/madouse.jpg
diff --git a/sbt/src/main/scala/org/cakesolutions/akkapatterns/main/ClientDemo.scala b/server/main/src/main/scala/org/cakesolutions/akkapatterns/main/ClientDemo.scala
similarity index 90%
rename from sbt/src/main/scala/org/cakesolutions/akkapatterns/main/ClientDemo.scala
rename to server/main/src/main/scala/org/cakesolutions/akkapatterns/main/ClientDemo.scala
index eedd5e4..3194264 100644
--- a/sbt/src/main/scala/org/cakesolutions/akkapatterns/main/ClientDemo.scala
+++ b/server/main/src/main/scala/org/cakesolutions/akkapatterns/main/ClientDemo.scala
@@ -1,19 +1,13 @@
package org.cakesolutions.akkapatterns.main
import akka.actor.{Props, ActorSystem}
-import com.aphelia.amqp.{ChannelOwner, ConnectionOwner}
-import com.aphelia.amqp.Amqp._
import akka.util.Timeout
import com.rabbitmq.client.{DefaultConsumer, Channel, Envelope, ConnectionFactory}
import com.rabbitmq.client.AMQP.BasicProperties
-import com.aphelia.amqp.Amqp.ReturnedMessage
-import com.aphelia.amqp.Amqp.Publish
-import com.aphelia.amqp.Amqp.ChannelParameters
-import scala.Some
-import com.aphelia.amqp.RpcClient.Request
-import com.aphelia.amqp.Amqp.QueueParameters
-import com.aphelia.amqp.Amqp.Delivery
import akka.actor.SupervisorStrategy.Stop
+import com.github.sstone.amqp._
+import com.github.sstone.amqp.RpcClient._
+import com.github.sstone.amqp.Amqp._
/**
* @author janmachacek
diff --git a/sbt/src/main/scala/org/cakesolutions/akkapatterns/main/Main.scala b/server/main/src/main/scala/org/cakesolutions/akkapatterns/main/Main.scala
similarity index 56%
rename from sbt/src/main/scala/org/cakesolutions/akkapatterns/main/Main.scala
rename to server/main/src/main/scala/org/cakesolutions/akkapatterns/main/Main.scala
index 27253ba..75224c4 100644
--- a/sbt/src/main/scala/org/cakesolutions/akkapatterns/main/Main.scala
+++ b/server/main/src/main/scala/org/cakesolutions/akkapatterns/main/Main.scala
@@ -1,17 +1,24 @@
package org.cakesolutions.akkapatterns.main
import akka.actor.ActorSystem
-import org.cakesolutions.akkapatterns.domain.Configuration
+import org.cakesolutions.akkapatterns.domain.{Settings, NoSqlConfig, Configuration}
import org.cakesolutions.akkapatterns.core.ServerCore
import org.cakesolutions.akkapatterns.web.Web
import org.cakesolutions.akkapatterns.api.Api
+import akka.util.Timeout
object Main {
def main(args: Array[String]) {
implicit val system = ActorSystem("AkkaPatterns")
- class Application(val actorSystem: ActorSystem) extends ServerCore with Configuration with Api with Web
+ class Application(val actorSystem: ActorSystem) extends Configuration with NoSqlConfig with ServerCore with Web {
+
+ implicit val timeout = Timeout(30000)
+
+ configure(mongo(Settings.main.db.mongo))
+ }
+
new Application(system)
diff --git a/server/project/build.properties b/server/project/build.properties
new file mode 100644
index 0000000..21e2c5d
--- /dev/null
+++ b/server/project/build.properties
@@ -0,0 +1 @@
+sbt.version=0.12.2
\ No newline at end of file
diff --git a/server/project/build.scala b/server/project/build.scala
new file mode 100644
index 0000000..0ac02e4
--- /dev/null
+++ b/server/project/build.scala
@@ -0,0 +1,148 @@
+import sbt._
+import Keys._
+import sbtscalaxb.Plugin._
+import ScalaxbKeys._
+import net.virtualvoid.sbt.graph.Plugin._
+import org.scalastyle.sbt._
+import com.typesafe.sbt.SbtStartScript
+
+// to sync this project with IntelliJ, run the sbt-idea plugin with: sbt gen-idea
+//
+// to set user-specific local properties, just create "~/.sbt/my-settings.sbt", e.g.
+// javaOptions += "some cool stuff"
+//
+// This project allows a local.conf on the classpath (e.g. domain/src/main/resources) to override settings, e.g.
+//
+// test.db.mongo.hosts { "Sampo.home": 27017 }
+// test.db.cassandra.hosts { "Sampo.home": 9160 }
+// main.db.mongo.hosts = ${test.db.mongo.hosts}
+// main.db.cassandra.hosts = ${test.db.cassandra.hosts}
+//
+// mkdir -p {domain,core,api,main,test}/src/{main,test}/{java,scala,resources}/org/cakesolutions/akkapatterns
+//
+// the following were useful for writing this file
+// http://www.scala-sbt.org/release/docs/Getting-Started/Multi-Project.html
+// https://github.com/sbt/sbt/blob/0.12.2/main/Build.scala
+// https://github.com/akka/akka/blob/master/project/AkkaBuild.scala
+object PatternsBuild extends Build {
+
+ override val settings = super.settings ++ Seq(
+ organization := "org.cakesolutions.patterns",
+ version := "1.0-SNAPSHOT",
+ scalaVersion := "2.10.1"
+ )
+
+ lazy val defaultSettings = Defaults.defaultSettings ++ graphSettings ++ Seq(
+ scalacOptions in Compile ++= Seq("-encoding", "UTF-8", "-target:jvm-1.6", "-deprecation", "-unchecked"),
+ javacOptions in Compile ++= Seq("-source", "1.6", "-target", "1.6", "-Xlint:unchecked", "-Xlint:deprecation", "-Xlint:-options"),
+ // https://github.com/sbt/sbt/issues/702
+ javaOptions += "-Djava.util.logging.config.file=logging.properties",
+ javaOptions += "-Xmx2G",
+ outputStrategy := Some(StdoutOutput),
+ fork := true,
+ maxErrors := 1,
+ resolvers ++= Seq(
+ Resolver.mavenLocal,
+ Resolver.sonatypeRepo("releases"),
+ Resolver.typesafeRepo("releases"),
+ "Spray Releases" at "http://repo.spray.io",
+ Resolver.typesafeRepo("snapshots"),
+ Resolver.sonatypeRepo("snapshots"),
+ "Jasper Community" at "http://jasperreports.sourceforge.net/maven2"
+ // resolvers += "neo4j repo" at "http://m2.neo4j.org/content/repositories/releases/"
+ ),
+ parallelExecution in Test := false
+ ) ++ ScctPlugin.instrumentSettings ++ scalaxbSettings ++ ScalastylePlugin.Settings
+
+ def module(dir: String) = Project(id = dir, base = file(dir), settings = defaultSettings)
+ import Dependencies._
+
+ // https://github.com/eed3si9n/scalaxb/issues/199
+ lazy val domain = module("domain") settings(
+ libraryDependencies += java_logging, // will upgrade to scala_logging when released
+ libraryDependencies += akka_contrib,
+ libraryDependencies += akka,
+ libraryDependencies += scala_io_core,
+ libraryDependencies += scala_io_file,
+ libraryDependencies += scalad,
+ libraryDependencies += hector,
+ libraryDependencies += spring_core,
+ libraryDependencies += specs2 % "test",
+ packageName in scalaxb in Compile := "org.cakesolutions.patterns.domain.soap",
+ sourceGenerators in Compile <+= scalaxb in Compile
+ )
+
+ lazy val test = module("test") dependsOn (domain) settings (
+ libraryDependencies += specs2 % "compile",
+ libraryDependencies += cassandra_unit,
+ libraryDependencies += neo4j,
+ libraryDependencies += akka_testkit % "compile"
+ )
+
+ lazy val core = module("core") dependsOn(domain, test % "test") settings (
+ libraryDependencies += spray_client,
+ libraryDependencies += amqp,
+ libraryDependencies += rabbitmq,
+ libraryDependencies += mail,
+ libraryDependencies += neo4j,
+ libraryDependencies += scalaz_effect,
+ libraryDependencies += jasperreports,
+ libraryDependencies += poi
+ )
+
+ lazy val api = module("api") dependsOn(core, test % "test") settings(
+ libraryDependencies += spray_routing,
+ libraryDependencies += spray_testkit % "test"
+ )
+
+ lazy val main = module("main") dependsOn(api, test % "test")
+
+ lazy val root = Project(id = "parent", base = file("."), settings = defaultSettings) settings (
+ ScctPlugin.mergeReportSettings: _*
+ ) settings (
+ SbtStartScript.startScriptForClassesSettings: _*
+ ) settings (
+ mainClass in (Compile, run) := Some("org.cakesolutions.akkapatterns.main.Main")
+ ) aggregate (
+ domain, test, core, api, main
+ ) dependsOn (main) // yuck
+}
+
+object Dependencies {
+ // to help resolve transitive problems, type:
+ // `sbt dependency-graph`
+ // `sbt test:dependency-tree`
+ val bad = Seq(
+ ExclusionRule(name = "log4j"),
+ ExclusionRule(name = "commons-logging"),
+ ExclusionRule(organization = "org.slf4j")
+ )
+
+ val akka_version = "2.1.2"
+ val spray_version = "1.1-M7"
+
+ val java_logging = "com.github.fommil" % "java-logging" % "1.0"
+ val scalad = "org.cakesolutions" %% "scalad" % "1.3.0-SNAPSHOT" // https://github.com/janm399/scalad/issues/7
+ val akka = "com.typesafe.akka" %% "akka-actor" % akka_version
+ val akka_contrib = "com.typesafe.akka" %% "akka-contrib" % akka_version intransitive()// JUL only
+ val akka_testkit = "com.typesafe.akka" %% "akka-testkit" % akka_version
+ val scalaz_effect = "org.scalaz" %% "scalaz-effect" % "7.0.0-M9"
+ val spring_core = "org.springframework" % "spring-core" % "3.1.4.RELEASE" excludeAll (bad: _*)
+ // beware Hector 1.1-2 and Guava 14: https://github.com/hector-client/hector/pull/591
+ val guava = "com.google.guava" % "guava" % "13.0.1" // includes Cache
+ val jsr305 = "com.google.code.findbugs" % "jsr305" % "2.0.1" // undeclared dep of Guava
+ val hector = "org.hectorclient" % "hector-core" % "1.1-2" excludeAll (bad: _*)
+ val spray_routing = "io.spray" % "spray-routing" % spray_version
+ val spray_client = "io.spray" % "spray-client" % spray_version
+ val spray_testkit = "io.spray" % "spray-testkit" % spray_version
+ val cassandra_unit = "org.cassandraunit" % "cassandra-unit" % "1.1.2.1" excludeAll (bad: _*)
+ val specs2 = "org.specs2" %% "specs2" % "1.13"
+ val amqp = "com.github.sstone" %% "amqp-client" % "1.1"
+ val rabbitmq = "com.rabbitmq" % "amqp-client" % "2.8.1"
+ val neo4j = "org.neo4j" % "neo4j" % "1.9.M05"
+ val jasperreports = "net.sf.jasperreports" % "jasperreports" % "5.0.1" excludeAll (bad: _*)
+ val poi = "org.apache.poi" % "poi" % "3.9"
+ val mail = "javax.mail" % "mail" % "1.4.2"
+ val scala_io_core = "com.github.scala-incubator.io" %% "scala-io-core" % "0.4.2"
+ val scala_io_file = "com.github.scala-incubator.io" %% "scala-io-file" % "0.4.2"
+}
\ No newline at end of file
diff --git a/server/project/plugins.sbt b/server/project/plugins.sbt
new file mode 100644
index 0000000..cc48177
--- /dev/null
+++ b/server/project/plugins.sbt
@@ -0,0 +1,17 @@
+resolvers += "Sonatype snapshots" at "http://oss.sonatype.org/content/repositories/releases/"
+
+addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.2.0")
+
+addSbtPlugin("org.scalaxb" % "sbt-scalaxb" % "1.0.1")
+
+//addSbtPlugin("com.typesafe.sbt" % "sbt-scalariform" % "1.0.0")
+
+addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.7.1")
+
+resolvers += "SCCT Snapshots" at "http://mtkopone.github.com/scct/maven-repo"
+
+addSbtPlugin("reaktor" % "sbt-scct" % "0.2-SNAPSHOT") // https://github.com/mtkopone/scct/issues/43
+
+addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "0.2.0")
+
+addSbtPlugin("com.typesafe.sbt" % "sbt-start-script" % "0.7.0")
\ No newline at end of file
diff --git a/server/provisioning/gatling/README.md b/server/provisioning/gatling/README.md
new file mode 100644
index 0000000..ba0e611
--- /dev/null
+++ b/server/provisioning/gatling/README.md
@@ -0,0 +1,7 @@
+Tested with Gatling 1.4.x
+
+0. Ensure your databases are correctly configured for running the main method.
+1. Download and extract [Gatling](https://github.com/excilys/gatling/wiki/Downloads).
+2. Create and copy `test.xml` into `user-files/request-bodies`.
+3. Copy the `patterns` folder into `user-files/simulations`.
+4. Run `bin/gatling.sh`.
diff --git a/server/provisioning/gatling/patterns/AkkaPatternsGatling.scala b/server/provisioning/gatling/patterns/AkkaPatternsGatling.scala
new file mode 100644
index 0000000..cb22b29
--- /dev/null
+++ b/server/provisioning/gatling/patterns/AkkaPatternsGatling.scala
@@ -0,0 +1,25 @@
+package patterns
+
+import com.excilys.ebi.gatling.core.Predef._
+import com.excilys.ebi.gatling.http.Predef._
+import com.excilys.ebi.gatling.jdbc.Predef._
+import com.excilys.ebi.gatling.http.Headers.Names._
+import akka.util.duration._
+import bootstrap._
+
+// https://github.com/excilys/gatling/wiki/First-Steps-with-Gatling
+// https://github.com/excilys/gatling/wiki/Advanced-Usage
+class AkkaPatternsGatling extends Simulation {
+ val httpConf = httpConfig.baseURL("http://localhost:8080").disableFollowRedirect
+ // val headers_login = Map("Content-Type" -> "application/json")
+ val exampleScn = scenario("Example Scenario")
+ .exec(
+ http("make_request")
+ .get("/")
+ // uncomment get() and swap for post() as needed
+ // .post("/").fileBody(test.xml")
+ )
+
+ setUp(exampleScn.users(15000).ramp(100).protocolConfig(httpConf))
+}
+
diff --git a/server/test/src/main/resources/org/cakesolutions/akkapatterns/testdata/common.js b/server/test/src/main/resources/org/cakesolutions/akkapatterns/testdata/common.js
new file mode 100644
index 0000000..47a4c24
--- /dev/null
+++ b/server/test/src/main/resources/org/cakesolutions/akkapatterns/testdata/common.js
@@ -0,0 +1,30 @@
+// adapted from https://github.com/mongodb/mongo-csharp-driver/blob/master/uuidhelpers.js
+
+HexToBase64 = function(hex) {
+ var base64Digits = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ var base64 = "";
+ var group;
+ for (var i = 0; i < 30; i += 6) {
+ group = parseInt(hex.substr(i, 6), 16);
+ base64 += base64Digits[(group >> 18) & 0x3f];
+ base64 += base64Digits[(group >> 12) & 0x3f];
+ base64 += base64Digits[(group >> 6) & 0x3f];
+ base64 += base64Digits[group & 0x3f];
+ }
+ group = parseInt(hex.substr(30, 2), 16);
+ base64 += base64Digits[(group >> 2) & 0x3f];
+ base64 += base64Digits[(group << 4) & 0x3f];
+ base64 += "==";
+ return base64;
+}
+JUUID = function (uuid) {
+ var hex = uuid.replace(/[{}-]/g, ""); // remove extra characters
+ var msb = hex.substr(0, 16);
+ var lsb = hex.substr(16, 16);
+ msb = msb.substr(14, 2) + msb.substr(12, 2) + msb.substr(10, 2) + msb.substr(8, 2) + msb.substr(6, 2) + msb.substr(4, 2) + msb.substr(2, 2) + msb.substr(0, 2);
+ lsb = lsb.substr(14, 2) + lsb.substr(12, 2) + lsb.substr(10, 2) + lsb.substr(8, 2) + lsb.substr(6, 2) + lsb.substr(4, 2) + lsb.substr(2, 2) + lsb.substr(0, 2);
+ hex = msb + lsb;
+ var base64 = HexToBase64(hex);
+ return new BinData(3, base64);
+}
+
diff --git a/server/test/src/main/resources/org/cakesolutions/akkapatterns/testdata/customers.js b/server/test/src/main/resources/org/cakesolutions/akkapatterns/testdata/customers.js
new file mode 100644
index 0000000..aebba90
--- /dev/null
+++ b/server/test/src/main/resources/org/cakesolutions/akkapatterns/testdata/customers.js
@@ -0,0 +1,13 @@
+db.customers.drop();
+db.customers.save([
+ {
+ "firstName":"Jan",
+ "lastName":"Machacek",
+ "email":"janm@cakesolutions.net",
+ "id":JUUID("122fa630-92fd-11e2-9e96-0800200c9a66"),
+ "addresses":[
+ {"line1":"Magdalen Centre", "line2":"Robert Robinson Avenue", "line3":"Oxford"},
+ {"line1":"Houldsworth Mill", "line2":"Houldsworth Street", "line3":"Reddish"}
+ ]
+ }
+]);
diff --git a/server/test/src/main/resources/org/cakesolutions/akkapatterns/testdata/users.js b/server/test/src/main/resources/org/cakesolutions/akkapatterns/testdata/users.js
new file mode 100644
index 0000000..cc9378e
--- /dev/null
+++ b/server/test/src/main/resources/org/cakesolutions/akkapatterns/testdata/users.js
@@ -0,0 +1,31 @@
+// we're using Neo4J for the users. but if we wanted to use Mongo instead, this is some useful test data
+
+db.users.drop();
+db.users.save([
+{
+ id: JUUID("994fc1f0-90a9-11e2-9e96-0800200c9a66"),
+ username: "guest",
+ hashedPassword: "",
+ email: "johndoe@example.com",
+ firstName: "John",
+ lastName: "Doe",
+ kind: {kind: "guest"}
+}, {
+ id: JUUID("7370f980-90aa-11e2-9e96-0800200c9a66"),
+ username: "customer",
+ hashedPassword: "",
+ email: "johndoe@example.com",
+ mobile: "07777777777",
+ firstName: "John",
+ lastName: "Doe",
+ kind: {kind: "customer", value: {customerReference: JUUID("67c2b250-92f2-11e2-9e96-0800200c9a66")}}
+},{
+ id: JUUID("c0a93190-90aa-11e2-9e96-0800200c9a66"),
+ username: "root",
+ hashedPassword: "",
+ email: "johndoe@example.com",
+ firstName: "John",
+ lastName: "Doe",
+ kind: {kind: "guest"}
+}
+]);
\ No newline at end of file
diff --git a/server/test/src/main/scala/org/cakesolutions/akkapatterns/specifications.scala b/server/test/src/main/scala/org/cakesolutions/akkapatterns/specifications.scala
new file mode 100644
index 0000000..8b2590e
--- /dev/null
+++ b/server/test/src/main/scala/org/cakesolutions/akkapatterns/specifications.scala
@@ -0,0 +1,116 @@
+package org.cakesolutions.akkapatterns
+
+import domain._
+import org.cassandraunit.DataLoader
+import org.cassandraunit.dataset.yaml.ClassPathYamlDataSet
+import com.mongodb.DB
+import me.prettyprint.hector.api.Cluster
+import org.specs2.mutable.Specification
+import collection.JavaConversions._
+import org.specs2.specification.{SpecificationStructure, Step, Fragments}
+import akka.contrib.jul.JavaLogging
+import org.specs2.control.StackTraceFilter
+import akka.testkit.TestKit
+import akka.actor.ActorSystem
+import akka.util.Timeout
+import org.cakesolutions.scalad.mongo.sprayjson.SprayMongo
+
+
+/** Convenient parent for all Specs, ensuring that exceptions are (mostly) correctly
+ * logged. This is necessary because Specs2 tries to do its own exception logging
+ * and ends up duplicating a lot of functionality already provided by J2SE.
+ *
+ * Provides access to a 'log' field.
+ *
+ * NOTE: don't forget to add `sequential` if your specs depend on ordering.
+ */
+abstract class NoActorSpecs extends Specification with JavaLogging {
+ // change Specification to SpecificationWithJUnit for JUnit integration (not needed with SBT anymore)
+
+ args.report(traceFilter = LoggedStackTraceFilter)
+}
+
+/** Convenient parent for Specs that test an Actor: logging is enabled, timeouts are set, specs
+ * run sequentially and the actor system is closed down after all specs have run.
+ *
+ * The logging backend helps to catch a lot of root causes, as typically a failed actor
+ * spec will result in a timeout in the spec plus a hidden-away Akka log revealing the true
+ * exception.
+ */
+//@RunWith(classOf[JUnitRunner])
+abstract class ActorSpecs extends TestKit(ActorSystem()) with Specification with JavaLogging {
+
+ args.report(traceFilter = LoggedStackTraceFilter)
+
+ sequential
+
+ implicit def self = testActor
+
+ implicit val timeout = Timeout(10000)
+
+ // https://groups.google.com/d/topic/specs2-users/PdCeX4zxc0A/discussion
+ override def map(fs: => Fragments) = super.map(fs) ^ Step(system.shutdown())
+}
+
+
+/** Provides access to a test MongoDB instance.
+ */
+trait TestMongo extends Configuration with Configured with NoSqlConfig with Resources {
+ this: Specification with JavaLogging =>
+
+ configure(mongo(Settings.test.db.mongo))
+
+ val mongo = new SprayMongo
+
+ def resetMongo() {
+ val db = configured[DB]
+ log.debug(s"resetting ${db.getName}")
+ db.dropDatabase()
+ }
+}
+
+/** Provides access to a test MongoDB instance that is cleaned up before any specs are run.
+ * Note that Specs will break if SBT runs them in parallel, so ensure `parallelExecution in Test := false`.
+ */
+trait CleanMongo extends TestMongo {
+ this: Specification with JavaLogging =>
+
+ // https://groups.google.com/d/topic/specs2-users/PdCeX4zxc0A/discussion
+ override def map(fs: => Fragments) = Step(resetMongo()) ^ fs
+}
+
+
+/** Convenient mixin that provides access to a cleanly prepared (before any spec is run) Cassandra.
+ */
+trait TestCassandra extends SpecificationStructure with Configuration with Configured with NoSqlConfig with Resources {
+ this: Specification with JavaLogging =>
+
+ configure(cassandra(Settings.test.db.cassandra))
+
+ def resetCassandra() {
+ log.info("resetting cassandra")
+ val cassandraBase = new ClassPathYamlDataSet("org/cakesolutions/akkapatterns/test/cassandra-base.yaml")
+ val cluster = configured[Cluster]
+ val name = cluster.describeClusterName()
+ val host = cluster.getKnownPoolHosts(false).head.getHost
+ new DataLoader(name, host).load(cassandraBase)
+ }
+}
+
+trait CleanCassandra extends TestCassandra {
+ this: Specification with JavaLogging =>
+
+ override def map(fs: => Fragments) = super.map(fs) ^ Step(resetCassandra())
+}
+
+// from com.github.fommil.scala-logging
+object LoggedStackTraceFilter extends StackTraceFilter with JavaLogging {
+ def apply(e: Seq[StackTraceElement]) = Nil
+
+ override def apply[T <: Exception](e: T): T = {
+ log.error(e, "Specs2")
+ // this only works because log.error will construct the LogRecord instantly
+ e.setStackTrace(new Array[StackTraceElement](0))
+ e
+ }
+}
\ No newline at end of file
diff --git a/server/test/src/main/scala/org/cakesolutions/akkapatterns/testdata.scala b/server/test/src/main/scala/org/cakesolutions/akkapatterns/testdata.scala
new file mode 100644
index 0000000..77c0826
--- /dev/null
+++ b/server/test/src/main/scala/org/cakesolutions/akkapatterns/testdata.scala
@@ -0,0 +1,61 @@
+package org.cakesolutions.akkapatterns
+
+import domain._
+import org.specs2.mutable.Before
+import com.mongodb.{BasicDBList, DBObject, DB}
+import com.mongodb.util.JSON
+import scala.collection.JavaConversions._
+import akka.contrib.jul.JavaLogging
+import java.util.UUID
+
+/*
+ * The idea with test data is to provide
+ *
+ * 1. Fixtures - can be attached to specs (individual or for a whole file)
+ * which define the state of the database at the beginning
+ * of the spec examples. Typically these are loaded per example.
+ * 2. Identifiers - can be used to lookup data that is inserted into the DB
+ * by the fixtures. Typically these are mixed in to specs.
+ * 3. Data Instances - which is suitable for programmatically inserting into
+ * the database. Typically these are mixed in to specs.
+ */
+
+object MongoCollectionFixture {
+ class Fix(names: String*) extends MongoCollectionFixture(true, names:_*)
+
+ class ContinueFix(names: String*) extends MongoCollectionFixture(false, names:_*)
+}
+
+/**
+ * Fixture that evaluates named files (in Mongo Javascript format) from the classpath.
+ *
+ * @param clean if true, will drop the database before running the fixture
+ * @param names
+ */
+class MongoCollectionFixture(clean: Boolean, names: String*) extends Configured with Resources with JavaLogging with Before {
+ override def before() {
+ if (clean)
+ configured[DB].dropDatabase()
+
+ val header = readResource(s"classpath:/org/cakesolutions/akkapatterns/testdata/common.js").mkString
+ names.foreach {
+ name =>
+ configured[DB].eval(
+ header +
+ readResource(s"classpath:/org/cakesolutions/akkapatterns/testdata/${name}.js").mkString
+ )
+ }
+ }
+}
+
+
+//trait TestUserData {
+// val TestGuestUserId = UUID.fromString("994fc1f0-90a9-11e2-9e96-0800200c9a66")
+// val TestCustomerUserId = UUID.fromString("7370f980-90aa-11e2-9e96-0800200c9a66")
+// val TestAdminUserId = UUID.fromString("c0a93190-90aa-11e2-9e96-0800200c9a66")
+//}
+
+trait TestCustomerData {
+ val TestCustomerJanId = UUID.fromString("122fa630-92fd-11e2-9e96-0800200c9a66")
+
+}
diff --git a/sbt/src/test/scala/org/cakesolutions/akkapatterns/ArchitectureSpec.scala b/server/test/src/test/scala/org/cakesolutions/akkapatterns/ArchitectureSpec.scala
similarity index 65%
rename from sbt/src/test/scala/org/cakesolutions/akkapatterns/ArchitectureSpec.scala
rename to server/test/src/test/scala/org/cakesolutions/akkapatterns/ArchitectureSpec.scala
index 0a762d8..3acc4d9 100644
--- a/sbt/src/test/scala/org/cakesolutions/akkapatterns/ArchitectureSpec.scala
+++ b/server/test/src/test/scala/org/cakesolutions/akkapatterns/ArchitectureSpec.scala
@@ -1,10 +1,10 @@
package org.cakesolutions.akkapatterns
-import org.specs2.mutable.Specification
import org.specs2.specification.Analysis
import org.specs2.analysis.ClassycleDependencyFinder
+import com.mongodb.DB
-class ArchitectureSpec extends Specification with Analysis with ClassycleDependencyFinder {
+class ArchitectureSpec extends NoActorSpecs with Analysis with ClassycleDependencyFinder with TestMongo {
"The architecture" should {
"Have properly defined layers" in {
@@ -20,4 +20,10 @@ class ArchitectureSpec extends Specification with Analysis with ClassycleDepende
}
}
+ "The mongo database" should {
+ "be configured" in {
+ configured[DB] must not beNull
+ }
+ }
+
}
diff --git a/server/test/src/test/scala/org/cakesolutions/akkapatterns/TestDataSpecs.scala b/server/test/src/test/scala/org/cakesolutions/akkapatterns/TestDataSpecs.scala
new file mode 100644
index 0000000..7bc8f43
--- /dev/null
+++ b/server/test/src/test/scala/org/cakesolutions/akkapatterns/TestDataSpecs.scala
@@ -0,0 +1,24 @@
+package org.cakesolutions.akkapatterns
+
+import org.cakesolutions.akkapatterns.domain._
+import com.mongodb.DB
+import org.cakesolutions.scalad.mongo.sprayjson._
+import java.util.UUID
+import org.cakesolutions.akkapatterns.MongoCollectionFixture._
+
+class TestDataSpecs extends NoActorSpecs with CleanMongo with CustomerMongo with TestCustomerData {
+
+ "Mongo Test Data" should {
+ "be clean at the beginning" in {
+ configured[DB].getCollectionNames.size === 0
+ }
+
+ "customers fixture should attach" in new Fix("customers") {
+ mongo.count[Customer]() must beGreaterThan(0L)
+ mongo.findOne[Customer]("id" :> TestCustomerJanId) must beLike {
+ case Some(customer) if customer.firstName == "Jan" => ok
+ }
+ mongo.findOne[Customer]("id" :> UUID.randomUUID()) === None
+ }
+ }
+}