First of all, I'm relatively new to scala.
Consider the following simple class NullLogService which logs results to console.
package services
import akka.actor._
import com.amazonaws.services.simpleemail.model.SendRawEmailResult
import model.Email
import scala.util.Try
object NullLogService {
case class Log(email: Email, amazonResult: Try[_])
case class LogError(email: Email, amazonException: Try[_])
}
class NullLogService extends Actor {
import NullLogService._
def receive = {
case Log(email, amazonResult) => println("Email to: " + email.to + ". AmazonResultSucess:" + amazonResult.isSuccess.toString + ". AmazonResult: " + amazonResult.get.toString)
case LogError(email, amazonException) => println("Error: Email to: " + email.to + ". AmazonException:" + amazonException.get.toString)
case default#_ => println("Default case: "+default.toString)
}
}
It prints Default case: Log(TO:email#email.com,Success({MessageId: Fake_Sent_OK})) always.
I don't know what is happening, because the types are the same (Log(Email,Try(_))) ! It should go to the "Log" case but always fall to the default case!
Driving me crazy.
Code involved
Main class:
import java.io.File
import java.util.concurrent.TimeUnit
import akka.actor.SupervisorStrategy.Resume
import akka.actor._
import akka.routing.RoundRobinPool
import akka.util.Timeout
import com.thenewmotion.akka.rabbitmq._
import com.typesafe.config.{ConfigFactory, ConfigParseOptions}
import model.Email
import services.EmailService.EmailInfo
import services.{EmailService, LogService, NullLogService}
import utils.StringUtils
import scala.concurrent.duration.Duration
object Main extends App {
implicit val system = ActorSystem()
val emailServiceRef: ActorRef = system.actorOf(RoundRobinPool(rate).withSupervisorStrategy(supervisorStrategy).props(Props(new EmailService)), "emailWorkerPool")
val logServiceRef = system.actorOf(RoundRobinPool(1).props(Props(new NullLogService)), "logWorker")
val email = new Email(stringMap.apply("USEREMAIL"), stringMap.apply("REPLYTO"), stringMap.apply("SUBJECT"), stringMap.apply("BODY"), unsubscribeURL)
emailServiceRef ! EmailInfo(email, logServiceRef)
}
EmailService:
package services
import akka.actor._
import com.amazonaws.services.simpleemail.model.SendRawEmailResult
import model.Email
import services.LogService.{Log, LogError}
object EmailService {
case class EmailInfo(email: Email, logServiceRef: ActorRef)
}
class EmailService extends Actor {
import EmailService._
def receive = {
case EmailInfo(email, logServiceRef) =>
val emailResult = new SendRawEmailResult
emailResult.setMessageId("Fake_Sent_OK" )
val amazonResult = Try( emailResult )
logServiceRef ! Log(email, amazonResult)
}
}
Presumably you haven't removed LogService.Log and LogService.LogError, or else you would have gotten a compilation error for EmailService. In this case NullLogService shouldn't define its own messages, but use LogService's ones:
package services
import akka.actor._
import com.amazonaws.services.simpleemail.model.SendRawEmailResult
import model.Email
import scala.util.Try
import LogService.{Log, LogError}
// no object NullLogService unless you need it for something else
class NullLogService extends Actor {
def receive = {
case Log(email, amazonResult) => println("Email to: " + email.to + ". AmazonResultSucess:" + amazonResult.isSuccess.toString + ". AmazonResult: " + amazonResult.get.toString)
case LogError(email, amazonException) => println("Error: Email to: " + email.to + ". AmazonException:" + amazonException.get.toString)
case default#_ => println("Default case: "+default.toString)
}
}
Then you can switch between different services which use the same protocol (say, NullLogService, Slf4jLogService, InMemoryLogService) without changing the imports every time.
Found the error. I had to change the logger class I was importing.
I use NullLogService now, not LogService.
package services
import akka.actor._
import com.amazonaws.services.simpleemail.model.SendRawEmailResult
import model.Email
//Change this --> import services.LogService.{Log, LogError} <---
//To the class you are using --v
import services.NullLogService.{Log, LogError}
object EmailService {
case class EmailInfo(email: Email, logServiceRef: ActorRef)
}
class EmailService extends Actor {
import EmailService._
def receive = {
case EmailInfo(email, logServiceRef) =>
val emailResult = new SendRawEmailResult
emailResult.setMessageId("Fake_Sent_OK" )
val amazonResult = Try( emailResult )
logServiceRef ! Log(email, amazonResult)
}
}
It is weird because there was no runtime errors (like "NullLogService not imported")
Related
I have a simple test case which is using TestActorRef and eventually to verify a timeout call of a method. Here is details of 3 file sources:
TestRestUtility.scala
import play.api.http.HttpVerbs
import play.api.libs.ws.WSClient
import javax.inject.Inject
import scala.concurrent.Future
class TestRestUtility #Inject()(ws: WSClient) extends HttpVerbs {
import scala.concurrent.ExecutionContext.Implicits.global
def getHealthStatus(): Future[Int] = {
ws.url("https://www.google.com").get().map { response =>
response.status
}
}
}
TestHealthCheckActor.scala
import akka.actor.{Actor, ActorLogging, Props}
import play.api.http.Status
import javax.inject.Inject
import scala.concurrent.Future
import scala.concurrent.duration.{DurationInt, FiniteDuration}
import scala.util.{Failure, Success}
object TestHealthCheckActor {
def props(testRestUtility: TestRestUtility): Props = {
Props(new TestHealthCheckActor(testRestUtility: TestRestUtility))
}
}
class TestHealthCheckActor #Inject()(testRestUtility: TestRestUtility)
extends Actor with ActorLogging with Status {
import context.dispatcher
val checkPeriod: FiniteDuration = 1.seconds
var apiStatus: Int = _
override def preStart(): Unit = {
context.system.scheduler.scheduleWithFixedDelay(
0.milliseconds,
checkPeriod,
self,
RefreshHealthStatus
)
}
override def receive: Receive = {
case RefreshHealthStatus =>
val health: Future[Int] = testRestUtility.getHealthStatus()
health.onComplete({
case Success(result) =>
result match {
case OK => apiStatus = Status.OK
case _ => apiStatus = Status.REQUEST_TIMEOUT
}
case Failure(e) =>
println(e)
})
}
}
TestSpec.scala
import akka.actor.{ActorSystem, PoisonPill}
import akka.testkit.{ImplicitSender, TestActorRef, TestKit}
import org.mockito.Mockito
import org.mockito.Mockito.{times, verify, when}
import org.scalatest
import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach}
import org.scalatest.concurrent.Eventually
import org.scalatest.matchers.should.Matchers
import org.scalatest.time.Span
import org.scalatest.wordspec.AnyWordSpecLike
import org.scalatestplus.mockito.MockitoSugar
import play.api.http.Status
import scala.concurrent.Future
class TestSpec extends TestKit(ActorSystem("HealthCheckActorSpec")) with AnyWordSpecLike with Matchers
with BeforeAndAfterEach with MockitoSugar with ImplicitSender with Eventually with BeforeAndAfterAll with Status with TestHarnessConstants {
import scala.concurrent.ExecutionContext.Implicits.global
var mockTestRestUtility: TestRestUtility = _
"HealthCheckActor" should {
"time out and call subscription api" in {
mockTestRestUtility = mock[TestRestUtility]
when(mockTestRestUtility.getHealthStatus()).thenReturn(Future(OK)).thenReturn(Future(OK)).thenReturn(Future(OK))
val healthCheckActorShortPeriod: TestActorRef[TestHealthCheckActor] = TestActorRef(TestHealthCheckActor.props(mockTestRestUtility))
eventually(timeout(Span(9, scalatest.time.Seconds)), interval(Span(1, scalatest.time.Seconds))) {
verify(mockTestRestUtility, Mockito.atLeast(3)).getHealthStatus()
}
healthCheckActorShortPeriod ! PoisonPill
}
}
}
As description about eventually in "scalatest-core_2.12-3.2.3-sources.jar", it tolerates unsuccessful attempts before giving up, so the test case is expected to have 3 calls the method getHealthStatus() to be successful as the returned value from mock. But I got a failed test case with below error message. I don't know why the method was called only one time:
testRestUtility.getHealthStatus();
Wanted *at least* 3 times:
-> at com.deere.isg.ingest.supporttool.testharness.TestSpec.$anonfun$new$8(TestSpec.scala:44)
But was 1 time:
-> at com.deere.isg.ingest.supporttool.testharness.TestHealthCheckActor$$anonfun$receive$1.applyOrElse(TestHealthCheckActor.scala:36)
org.mockito.exceptions.verification.TooFewActualInvocations:
testRestUtility.getHealthStatus();
Wanted *at least* 3 times:
-> at com.deere.isg.ingest.supporttool.testharness.TestSpec.$anonfun$new$8(TestSpec.scala:44)
But was 1 time:
-> at com.deere.isg.ingest.supporttool.testharness.TestHealthCheckActor$$anonfun$receive$1.applyOrElse(TestHealthCheckActor.scala:36)
Future(OK) will be considered successful by eventually because it doesn't throw an exception (technically neither would a failed Future, unless you unwrapped it in the eventually). Since the first call succeeded, eventually won't make any attempts after the first.
Basically I trying to get data from database using akka http. if i pass (EmployeeRepo.findAll()) directly in api then it show all data but while using actor it shows cast error....
here problem for data fetching only please solve it
this is my EmployeeRepo-----------------------------------
package org.repo
import org.data.Employee
import org.db.DbConfig
import org.mongodb.scala.MongoCollection
import org.utils.JsonUtils
import org.mongodb.scala.bson.conversions.Bson
import org.mongodb.scala.model.Filters.equal
import org.mongodb.scala.model.FindOneAndUpdateOptions
import org.mongodb.scala.model.Updates.{combine, set}
import scala.concurrent.Future
object EmployeeRepo extends JsonUtils{
private val employeeDoc:MongoCollection[Employee]=DbConfig.employees
def createCollection()={
DbConfig.database.createCollection("employee").subscribe(
(result)=>println(s"$result"),
e=>println(e.getLocalizedMessage),
()=>println("complete")
)
}
def insertData(emp:Employee)={
employeeDoc.insertOne(emp).toFuture()
}
def findAll()= employeeDoc.find().toFuture()
def update(emp:Employee,id:String)=
employeeDoc
.findOneAndUpdate(equal("_id", id),
setBsonValue(emp),
FindOneAndUpdateOptions().upsert(true)).toFuture()
def delete(id:String)=
employeeDoc.deleteOne(equal("_id",id)).toFuture()
private def setBsonValue(emp:Employee)={
combine(
set("name",emp.name),
set("dateOfBirth",emp.dateOfBirth)
)
}
}
============EMployeeService------------
package org.service
import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.util.UUID
import akka.NotUsed
import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.Source
import org.data.Employee
import org.domain.EmployeeRequest
import org.repo.EmployeeRepo
import scala.concurrent.Future
class EmployeeService {
implicit val system=ActorSystem("Employeee")
implicit val materializer=ActorMaterializer
import system.dispatcher
def saveEmployeeData= (employeeRequest:EmployeeRequest) => {
val employeeDoc:Employee=employeeMapperWithNewId(employeeRequest)
EmployeeRepo.insertData(employeeDoc)
}
def findAll={
EmployeeRepo.findAll()
}
def update(employeeRequest: EmployeeRequest,id:String)={
val employeeDoc=employeeMapperWithNewId(employeeRequest)
EmployeeRepo.update(emp = employeeDoc,id)
}
def delete(id:String)=EmployeeRepo.delete(id)
private def employeeMapperWithNewId(employee:EmployeeRequest)={
Employee(name=employee.name,dateOfBirth = LocalDate.parse(employee.dateOfBirth, DateTimeFormatter.ISO_DATE),
_id=UUID.randomUUID.toString)
}
}
-----------------EmployeeActor------
package org.actor
import akka.actor.{Actor, ActorLogging}
import org.actor.EmployeeActor.{Delete, Save, SearchAll, Update}
import org.data.Employee
import org.domain.EmployeeRequest
import org.service.EmployeeService
object EmployeeActor {
case class Save(emp: EmployeeRequest)
case object SearchAll
case class Update(emp: EmployeeRequest, id: String)
case class Delete(id: String)
}
class
EmployeeActor extends Actor with ActorLogging {
private val employeeService: EmployeeService = new EmployeeService()
override def receive: Receive = {
case Save(emp) =>
log.info(s"recevied msg saved with employee :$emp")
sender() ! employeeService.saveEmployeeData(emp)
case SearchAll =>
log.info("received msg find ALL")
sender() ! employeeService.findAll
case Update(emp, id) =>
log.info(s"received update msg for id $id and employee $emp")
sender() ! employeeService.update(emp, id)
case Delete(id) =>
log.info(s"received msg for deleting employee of id $id")
sender() ! employeeService.delete(id)
case _ =>
log.debug("Unhandled msg")
}
}
---------------EmployeeRoute----
package org
import akka.actor.{ActorSystem, Props}
import akka.http.scaladsl.model.{ContentTypes, HttpEntity, StatusCodes}
import akka.stream.ActorMaterializer
import org.actor.EmployeeActor
import org.utils.{JsonUtils, TimeUtils}
import akka.http.scaladsl.server.Directives._
import akka.{NotUsed, util}
import akka.util.{ByteString, Timeout}
import org.data.Employee
import org.domain.EmployeeRequest
import akka.pattern.{Patterns, ask}
import akka.stream.scaladsl.Source
import org.actor.EmployeeActor.{Delete, Save, SearchAll, Update}
import org.service.EmployeeService
import scala.concurrent.duration._
import spray.json._
import scala.concurrent.{Await, Future}
class EmployeeRoute extends JsonUtils{
implicit val system=ActorSystem("Employee")
implicit val materializer=ActorMaterializer
import system.dispatcher
val actor=system.actorOf(Props[EmployeeActor],"employeeActor")
val employeeService=new EmployeeService()
implicit val timeOut=Timeout(5.seconds)
val getRoute={
pathPrefix("employee"){
(pathEndOrSingleSlash & get){
complete((actor ? SearchAll).mapTo[Seq[EmployeeRequest]])
}~
( path("update") & put & parameter("id".as[String])){id=>
entity(as[EmployeeRequest]){employee=>
complete((actor ? Update(employee,id)).map(_=>StatusCodes.OK))
}
}~
post{
entity(as[EmployeeRequest]) { employee =>
complete((actor ? Save(employee)).map(_ => StatusCodes.OK))
}
}~
delete{
(path(Segment) |parameter("id".as[String])){id=>
complete((actor ? Delete(id)).map(_=>StatusCodes.OK))
}
}
}
}
}
=================Error===============
[ERROR] [09/09/2020 19:46:48.551] [web-app-akka.actor.default-dispatcher-4] [akka.actor.ActorSystemImpl(web-app)] Error during processing of request: 'Cannot cast scala.concurrent.impl.Promise$Transformation to scala.collection.immutable.Seq'. Completing with 500 Internal Server Error response. To change default exception handling behavior, provide a custom ExceptionHandler.
java.lang.ClassCastException: Cannot cast scala.concurrent.impl.Promise$Transformation to scala.collection.immutable.Seq
at java.base/java.lang.Class.cast(Class.java:3734)
at scala.concurrent.Future.$anonfun$mapTo$1(Future.scala:464)
at scala.concurrent.impl.Promise$Transformation.run(Promise.scala:430)
at scala.concurrent.ExecutionContext$parasitic$.execute(ExecutionContext.scala:164)
at scala.concurrent.impl.Promise$Transformation.submitWithValue(Promise.scala:392)
at scala.concurrent.impl.Promise$DefaultPromise.submitWithValue(Promise.scala:299)
at scala.concurrent.impl.Promise$DefaultPromise.tryComplete0(Promise.scala:249)
at scala.concurrent.impl.Promise$DefaultPromise.tryComplete(Promise.scala:242)
at akka.pattern.PromiseActorRef.$bang(AskSupport.scala:615)
at org.actor.EmployeeActor$$anonfun$receive$1.applyOrElse(EmployeeActor.scala:32)
at akka.actor.Actor.aroundReceive(Actor.scala:537)
at akka.actor.Actor.aroundReceive$(Actor.scala:535)
at org.actor.EmployeeActor.aroundReceive(EmployeeActor.scala:22)
at akka.actor.ActorCell.receiveMessage(ActorCell.scala:577)
at akka.actor.ActorCell.invoke(ActorCell.scala:547)
at akka.dispatch.Mailbox.processMailbox(Mailbox.scala:270)
at akka.dispatch.Mailbox.run(Mailbox.scala:231)
at akka.dispatch.Mailbox.exec(Mailbox.scala:243)
at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1016)
at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1665)
at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1598)
at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:183)
please help me out..
You analyzed almost everything. EmployeeRepo.findAll is your problem. You should not use Futures in akka actors. pipeTo should be used instead.
Please try to update EmployeeActor
case SearchAll =>
log.info("received msg find ALL")
employeeService.findAll.pipeTo(sender())
I am trying to replicate the basic example proposed in the Integrating with Akka, Play 2.4 for Scala doc. But I have difficulties in placing the final pieces together...
I have defined the actor (see paragraph Writing actors) at app/actors/HelloActor.scala with the following code:
package actors
import akka.actor._
object HelloActor {
def props = Props[HelloActor]
case class SayHello(name: String)
}
class HelloActor extends Actor {
import HelloActor._
def receive = {
case SayHello(name: String) =>
sender() ! "Hello, " + name
}
}
Then (see Creating and using actors) I suppose I should create a controller at app/controllers/Hello.scala with something like:
package controllers
import play.api.mvc._
import akka.actor._
import javax.inject._
import actors.HelloActor
#Singleton
class Hello #Inject() (system: ActorSystem) extends Controller {
val helloActor = system.actorOf(HelloActor.props, "hello-actor")
...
}
The question: where and how I utilize the code in the following paragraph Asking things of actors to have a working solution? I have tried to add it to the above Hello.scala controller but without success.
import play.api.libs.concurrent.Execution.Implicits.defaultContext
import scala.concurrent.duration._
import akka.pattern.ask
implicit val timeout = 5.seconds
def sayHello(name: String) = Action.async {
(helloActor ? SayHello(name)).mapTo[String].map { message =>
Ok(message)
}
}
Found the solution, I had some problems with defining the implicit timeout, this is the working controller:
package controllers
import play.api.mvc._
import akka.actor._
import javax.inject._
import actors.HelloActor
import actors.HelloActor.SayHello
import play.api.libs.concurrent.Execution.Implicits.defaultContext
import scala.concurrent.duration._
import akka.pattern.ask
import akka.util.Timeout
#Singleton
class Hello #Inject() (system: ActorSystem) extends Controller {
val helloActor = system.actorOf(HelloActor.props, "hello-actor")
implicit val timeout: Timeout = 5.seconds
def sayHello(name: String) = Action.async {
(helloActor ? SayHello(name)).mapTo[String].map { message ⇒
Ok(message)
}
}
}
Plus I added the following route in app/conf/routes:
# Actor test
GET /hello/:name controllers.Hello.sayHello(name)
Sorry for my bad english!
I'm newbie in scala language, I'm compiling a project with some modifications and getting the error
"scala value is not member of"... some body can help me? follow my code:
Command.scala
package com.bla.cms.domain
import scala.collection.Map
import redis.clients.jedis.Jedis
sealed trait CommandResult
object Success extends CommandResult
object Failure extends CommandResult
object Unauthorized extends CommandResult
trait Command {
def captchaValidator:Validator
def logger:Logging
val properties = PropertiesHandler.readProperties(System.getProperty(PropertiesHandler.configFileNameKey))
}
RedisIcrementCommand.scala
package com.bla.cms
package domain
import scala.collection.Map
import redis.clients.jedis.JedisPool
import dispatch._
import org.apache.log4j.Logger
import redis.clients.jedis.Jedis
class RedisIncrementCommand(database: Jedis, val captchaValidator:Validator, val logger:Logging) extends Command {
def execute(parameters: Map[String,String]):CommandResult = {
captchaValidator.validate(parameters) match {
case Right(params) =>
executeInDatabase(params)
case Left(status) =>
logger.info("Falha ao tentar executar comando. Captcha inválido. Status: " + status)
Failure
}
}
def executeInDatabase(parameters: Map[String,String]):CommandResult = {
parameters.get("selectedAnswer") match {
case Some(selectedAnswer) =>
database.incr(selectedAnswer)
database.sadd(Answers.key, selectedAnswer)
logger.info("Incremento recebido com sucesso. Opção: " + selectedAnswer)
Success
case None =>
logger.info("Falha ao tentar computar incremento. Alternativa não informada.")
Failure
}
}
}
PollServle.scala
package com.bla.cms
import javax.servlet.http._
import redis.clients.jedis.JedisPool
import scala.collection.JavaConverters._
import scala.collection.JavaConversions._
import com.bla.cms.domain.Logging
import org.apache.log4j.Logger
import scala.util.matching.Regex
class PollServlet extends HttpServlet with RequiresConnection {
import com.bla.cms.domain._
import URLHandler._
override def doPost(request:HttpServletRequest, response:HttpServletResponse):Unit = {
val parametersMap = new ServletApiAdapter(request,response).parameters
val outcome = newIncrementCommand(request).execute(parametersMap)
redirect(request, response, outcome)
}
}
And when I compile, I've got:
[error]PollServlet.scala:19: value execute is nota member of com.bla.cms.domain.Command
val outcome = newIncrementCommand(request).execute(parametersMap)
Somebody can help me please?
You can:
Add def execute(parameters: Map[String,String]):CommandResult to Command trait.
Change newIncrementCommand(request) to return RedisIncrementCommand instead of Command.
Cast result of newIncrementCommand(request) to RedisIncrementCommand using asInstanceOf.
I prefer 1 which would be:
trait Command {
val captchaValidator: Validator
val logger: Logging
val properties = PropertiesHandler.readProperties(System.getProperty(PropertiesHandler.configFileNameKey))
def execute(parameters: Map[String,String]): CommandResult
def executeInDatabase(parameters: Map[String,String]): CommandResult
}
My environment is eclipse indigo and I am trying to add a custom dispatcher.
import akka.actor.Actor
import akka.actor.ActorRef
import akka.dispatch.MessageDispatcher
import akka.dispatch._
import akka.event.Logging
import akka.actor.ActorSystem
import akka.actor.Props
import akka.dispatch;
class MyActor extends Actor {
Console.println("World!");
val log = Logging(context.system, this)
def receive = {
case "test" => log.info("received test")
case _ => log.info("received unknown message")
}
}
object Main extends App {
val system = ActorSystem("MySystem")
Console.println("Hello");
val myActor = system.actorOf(Props[MyActor].withDispatcher("my-dispatcher"), name = "myactor")
}
I am following this example. http://doc.akka.io/docs/akka/2.0.2/scala/dispatchers.html
I've already tried adding it to application.config in akka-2.0.2/config/application.config
In your project just put application.conf in the src/main/resources folder. That should work.