I want to use Akka scheduler to do some cron jobs in my Play application.
Since Play 2.4 GlobalSettings is not recommended. Anyone has some sample code on how to do that?
Even I tried the simple code from Play docs and it still not working.
class CustomApplicationLoader extends GuiceApplicationLoader() {
val logger: Logger = Logger(this.getClass)
override def builder(context: ApplicationLoader.Context): GuiceApplicationBuilder = {
logger.info("start")
val extra = Configuration("a" -> 1)
initialBuilder
.in(context.environment)
.loadConfig(extra ++ context.initialConfiguration)
.overrides(overrides(context): _*)
}
}
play.application.loader = "com.xxx.CustomApplicationLoader"
Why I can't get the logging message to print?
How I can use Akka on Application start?
This is how do it in my app:
Start of by defining a trait Scheduled.
package scheduled
import akka.actor.Cancellable
trait Scheduled {
var cancellable: Option[Cancellable] = None
val defaultInterval = 60
val secondsToWait = {
import scala.concurrent.duration._
10 seconds
}
def start()
def stop() = {
cancellable.map(_.cancel())
}
}
Then implement Scheduled with Akka magic
package scheduled
import akka.actor.ActorSystem
import play.api.{Configuration, Logger}
import com.google.inject.{Singleton, Inject}
import play.api.libs.concurrent.Execution.Implicits._
trait ScheduledWorker extends Scheduled
#Singleton
class ScheduledWorkerImpl #Inject()(
actorSystem: ActorSystem,
configuration: Configuration
) extends ScheduledWorker {
start()
lazy val intervalKey = "worker.interval"
lazy val jobEnabled = "worker.enabled"
override def start(): Unit = {
import scala.concurrent.duration._
lazy val i = configuration.getInt(intervalKey).getOrElse(defaultInterval)
lazy val isEnabled = Option(System.getProperty(jobEnabled)).getOrElse(
configuration.getString(jobEnabled).getOrElse("false")
).equals("true")
cancellable = isEnabled match {
case true =>
Some(
actorSystem.scheduler.schedule(0 seconds, i minutes) {
.. MAJOR COOL CODE!!! ;))) ...
}
)
case _ => None
}
}
}
create a module to eagerly start the scheduled stuff
package modules
import play.api.{Configuration, Environment}
import play.api.inject.Module
import scheduled.{ScheduledWorker}
class ScheduledModule extends Module {
def bindings(environment: Environment,
configuration: Configuration) = Seq(
bind[ScheduledWorker].to[ScheduledWorkerImpl].eagerly()
)
}
make sure that your config specifies ScheduledModule.
play.modules.enabled += "modules.ScheduledModule"
And voila you have a working scheduled task when your play 2.4 app starts =)
Related
I have this application using Play framework with Scala and I want to have a service that executes a method on startup of my application. I am doing everything that is said at How do I perform an action on server startup in the Scala Play Framework?. My version of Play is 2.6 and I am not using GlobalSettings for this.
package bootstrap
import com.google.inject.AbstractModule
class EagerLoaderModule extends AbstractModule {
override def configure() = {
println("EagerLoaderModule.configure")
bind(classOf[InitSparkContext]).to(classOf[InitSparkContextImpl]).asEagerSingleton()
}
}
On the application.conf I included the line play.modules.enabled += "bootstrap.EagerLoaderModule". Below is my Service that I want to start Spark context.
package bootstrap
import javax.inject.{Inject, Singleton}
import play.api.inject.ApplicationLifecycle
import scala.concurrent.Future
trait InitSparkContext {
def init(): Unit
def stop(): Unit
}
#Singleton
class InitSparkContextImpl #Inject()(appLifecycle: ApplicationLifecycle) extends InitSparkContext {
override def init(): Unit = println("InitSparkContext.start")
override def stop(): Unit = println("InitSparkContext.stop")
appLifecycle.addStopHook { () =>
stop()
Future.successful(())
}
init()
}
Nothing is printed on the console, even println("EagerLoaderModule.configure") is not printed....
You can't use println on play application, you'll need to set up a Logger. So:
val log = play.api.Logger(getClass)
log.info("This happened")
Then you can use the logback file to configure your log files:
Here are some details on how to configure it:
https://www.playframework.com/documentation/2.6.x/SettingsLogger
In "Dependency Injecting Actors" it's shown how to inject a parameter into the constructor of a child actor. The parent actor uses injectedChild to be allowed to pass to the child (at child creation time) only the non-injected parameter and then let Guice inject the rest. To do this, it extends InjectedActorSupport and gets the child's factory injected in the constructor:
class MyParent #Inject() (childFactory: MyChild.Factory,
#Assisted something: Something,
#Assisted somethingElse: SomethingElse) extends Actor with InjectedActorSupport
[..]
val child: ActorRef = injectedChild(childFactory(something, somethingElse), childName)
But what about the class that starts the parent and is not an actor but a custom ApplicationLoader?
How can I start the parent actor from there? No mention of this is in the documentation.
I tried doing the same for the loader as I did for parent:
class MyLoader #Inject() (parentFactory: MyParent.Factory) extends ApplicationLoader with Actor with InjectedActorSupport {
[..]
val parent = injectedChild(parentFactory(something, somethingElse), parentName)
would this be correct? How can I test it?
class MyModule extends AbstractModule with AkkaGuiceSupport {
def configure = {
bindActor[MyParent](parentName)
bindActor[MyLoader](loaderName)
bindActorFactory[MyChild, MyChild.Factory]
bindActorFactory[MyParent, MyParent.Factory]
}
}
So:
How do I start the parent from MyLoader while letting Guice dependency-inject what's required?
How can I test MyLoader?
This has been my test so far but now I need to pass the injected thingy to MyLoader and I don't know how (note the ***???**** in place of the argument which I do not know where to find):
class MyLoaderSpec(_system: ActorSystem, implicit val ec: ExecutionContext) extends TestKit(_system) with WordSpecLike with BeforeAndAfterAll with Matchers {
val loader = new SimstimLoader(???)
override def beforeAll(): Unit = {
loader.load(ApplicationLoader.createContext(new Environment(new File("."), ApplicationLoader.getClass.getClassLoader, Mode.Test)))
}
Thanks a million in advance!
Here is how I solved this issue.
--> How to start a parent actor who needs dependency-injection.
First of all, manually starting such an actor is impossible if you, like me, need to dependency-inject an instance which you do not know how to pass and where from. The solution is to let Guice start the actor automagically. Here is how.
First, create your binder module for Guice:
class MyModule extends AbstractModule with AkkaGuiceSupport{
override def configure(): Unit = {
bindActor[Root](Root.NAME)
bind(classOf[StartupActors]).asEagerSingleton()
}
}
Then, tell Play where your binder module is located by adding the following in your conf/application.conf:
play.modules={
enabled += "my.path.to.MyModule"
}
The StartupActors is simply a class I use to log whenever the automagic start of dependency-injected actors actually takes place. I log the event so that I can be sure of when and whether it occurs:
class StartupActors #Inject() (#Named(Root.NAME) root: ActorRef) {
play.api.Logger.info(s"Initialised $root")
}
The Root actor in my case takes care of parsing a custom configuration. Since the resulting vars from the parsing is required by my parent actor and during the tests I need to mock such resulting vars, I delegate the parsing to an actor other than the parent actor, i.e., the Root actor:
object Root {
final val NAME = "THERoot"
case class ParseConfiguration()
}
class Root #Inject()(configuration: Configuration, projectDAO: ProjectDAO) extends Actor {
val resultingVar: Something = myConfigParsing()
override def preStart(): Unit = {
context.actorOf(Props(new MyParent(resultingVar: Something, somethingElse: SomethingElse, projectDAO: ProjectDAO)))
}
override def receive: Receive = {
case ParseConfiguration => sender ! myConfigParsing()
case _ => logger.error("Root actor received an unsupported message")
}
}
The ParseConfiguration message is used uniquely for testing purposes. Normally the configuration parsing occurs instead because of the initialisation of the resultingVar attribute.
This way, MyParent wont need to get anything injected. Only StartupActors and Root will get injected. MyParent will simply get projectDAO from Root and pass it on to all its children.
class MyParent(something: Something, somethingElse: SomethingElse, projectDAO: ProjectDAO) extends Actor { ... }
Finally, for completion, I'm reporting here how I wrote the tests since I had troubles finding enough information online around this as well.
import akka.actor.{ActorRef, ActorSystem, Props}
import akka.testkit.{TestKit, TestProbe}
import com.typesafe.config.ConfigFactory
import org.mockito.Mockito.mock
import org.scalatest.{BeforeAndAfterAll, WordSpecLike}
import org.specs2.matcher.MustMatchers
import play.api.Configuration
import scala.concurrent.ExecutionContext
class RootSpec(_system: ActorSystem) extends TestKit(_system)
with WordSpecLike with BeforeAndAfterAll with MustMatchers {
implicit val ec: ExecutionContext = scala.concurrent.ExecutionContext.global
val conf: com.typesafe.config.Config = ConfigFactory.load()
val configuration: Configuration = Configuration(conf)
val projectDAOMock: ProjectDAO = mock(classOf[ProjectDAO])
private var mainActor: ActorRef = _
private var something: Something = Something.empty
def this() = this(ActorSystem("MySpec"))
override def afterAll: Unit = {
system.shutdown()
}
override def beforeAll(): Unit = {
mainActor = system.actorOf(Props(new Root(configuration, projectDAOMock)), Root.NAME)
}
"RootSpec: Root Actor" should {
val probe = TestProbe()
"successfully parse the configuration file" in {
probe.send(mainActor, ParseConfiguration)
something = probe.expectMsgPF() {
case msg => msg.asInstanceOf[Something]
}
}
}
}
and then I test MyParent by conveniently providing mock objects in place of vars resulting from the configuration parsing:
import akka.actor.{ActorRef, ActorSystem, Props}
import akka.testkit.{TestKit, TestProbe}
import org.mockito.Mockito
import org.mockito.Mockito._
import org.scalatest.{BeforeAndAfterAll, WordSpecLike}
import org.specs2.matcher.MustMatchers
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.{ExecutionContext, Future}
case class AnyProjectAPI(val projectAPI: ProjectAPI) extends AnyVal
class MyParentSpec(_system: ActorSystem, implicit val ec: ExecutionContext) extends TestKit(_system)
with WordSpecLike with BeforeAndAfterAll with MustMatchers {
val something = mock(classOf[Something])
val somethingElse = mock(classOf[somethingElse])
val projectDAOMock: ProjectDAO = mock(classOf[ProjectDAO])
val projectTest: ProjectAPI = new ProjectAPI(allMyRandomConstructorArguments),
val projectsList: List[ProjectAPI] = List(projectTest)
val expectedCreationId = 1
private var parent: ActorRef = _
def this() = this(ActorSystem("MySpec"), scala.concurrent.ExecutionContext.global)
override def afterAll: Unit = {
system.shutdown()
}
override def beforeAll(): Unit = {
parent = system.actorOf(Props(new MyParent(something, somethingElse, projectDAOMock)), MyParent.NAME)
}
"MyParentTesting: parent's pull request" should {
when(myProjApi.getAllProjects).thenReturn(Future {projectsList})
val anyProject: AnyProjectAPI = AnyProjectAPI(org.mockito.Matchers.any[ProjectAPI])
Mockito.when(projectDAOMock.create(org.mockito.Matchers.any[ProjectAPI]))
.thenReturn(Future {expectedCreationId}: Future[Int])
val probe = TestProbe()
val probe1 = TestProbe()
"be successfully satisfied by all children when multiple senders are waiting for an answer" in {
probe.send(parent, UpdateProjects)
probe1.send(parent, UpdateProjects)
allChildren.foreach(child =>
probe.expectMsg(expectedCreationId))
allChildren.foreach(child =>
probe1.expectMsg(expectedCreationId))
}
}
}
I want to create a scheduled task in Play 2.5. I found some resources related to this topic but none of them were for Play 2.5. I found out this resource related to what I am looking for and it looks good. Also on the same link there is a migration guide from 2.4 to 2.5.
The examples from older versions used GlobalSettings as base but this was deprecated in 2.5. The migration guide is important because it says that we should use dependency injection instead of extending this trait. I am not sure how to do that.
Can you give me some guidance?
You need to run sheduled task inside Akka Actor:
SchedulerActor.scala
package scheduler
import javax.inject.{Inject, Singleton}
import akka.actor.Actor
import org.joda.time.DateTime
import play.api.Logger
import scala.concurrent.ExecutionContext
#Singleton
class SchedulerActor #Inject()()(implicit ec: ExecutionContext) extends Actor {
override def receive: Receive = {
case _ =>
// your job here
}
}
Scheduler.scala
package scheduler
import javax.inject.{Inject, Named}
import akka.actor.{ActorRef, ActorSystem}
import play.api.{Configuration, Logger}
import scala.concurrent.ExecutionContext
import scala.concurrent.duration._
class Scheduler #Inject() (val system: ActorSystem, #Named("scheduler-actor") val schedulerActor: ActorRef, configuration: Configuration)(implicit ec: ExecutionContext) {
val frequency = configuration.getInt("frequency").get
var actor = system.scheduler.schedule(
0.microseconds, frequency.seconds, schedulerActor, "update")
}
JobModule.scala
package modules
import com.google.inject.AbstractModule
import play.api.libs.concurrent.AkkaGuiceSupport
import scheduler.{Scheduler, SchedulerActor}
class JobModule extends AbstractModule with AkkaGuiceSupport {
def configure() = {
bindActor[SchedulerActor]("scheduler-actor")
bind(classOf[Scheduler]).asEagerSingleton()
}
}
application.conf
play.modules.enabled += "modules.JobModule"
If you don't want to use akka, you can use java:
ScheduledFuture
ScheduledExecutorService
DemoDaemon.scala:
import java.util.concurrent.{Executors, ScheduledFuture, TimeUnit}
import javax.inject._
import play.Configuration
import scala.util.Try
class DemoDaemon #Inject() (conf: Configuration) {
val isEnabled = conf.getBoolean("daemon.enabled")
val delay = conf.getLong("daemon.delay")
private var scheduledTaskOption : Option[ScheduledFuture[_]] = None
def task(): Unit = {
Try {
println("doSomething")
} recover {
case e: Throwable => println(e.getMessage)
}
}
def start(): Unit = {
if (isEnabled) {
val executor = Executors.newScheduledThreadPool(1)
scheduledTaskOption = Some(
executor.scheduleAtFixedRate(
new Runnable {
override def run() = task()
},
delay, delay, TimeUnit.SECONDS
)
)
} else {
println("not enabled")
}
}
def stop(): Unit = {
scheduledTaskOption match {
case Some(scheduledTask) =>
println("Canceling task")
val mayInterruptIfRunning = false
scheduledTask.cancel(mayInterruptIfRunning)
case None => println("Stopped but was never started")
}
}
}
DaemonService.scala
import javax.inject.Inject
import play.api.inject.ApplicationLifecycle
import scala.concurrent.Future
class DaemonService #Inject() (appLifecycle: ApplicationLifecycle, daemon: DemoDaemon) {
daemon.start()
appLifecycle.addStopHook{ () =>
Future.successful(daemon.stop())
}
}
JobModule.scala
import com.google.inject.AbstractModule
class JobModule extends AbstractModule {
def configure(): Unit = {
bind(classOf[DaemonService]).asEagerSingleton()
}
}
application.conf
daemon.enabled = true
daemon.delay = 10
play.modules.enabled += "com.demo.daemon.JobModule"
I use scalaxb to generate models and client part of the SOAP interface. For testing I use Betamax, which can also be used in Scala. However, scalaxb uses Netty as a transport, which ignores proxy settings set up by Betamax. How would you cope with this situation?
scalaxb uses cake pattern, so the service is built from 3 parts like in the following example:
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent._
import scala.concurrent.duration._
val service = (new stockquote.StockQuoteSoap12Bindings with
scalaxb.SoapClientsAsync with
scalaxb.DispatchHttpClientsAsync {}).service
val fresponse = service.getQuote(Some("GOOG"))
val response = Await.result(fresponse, 5 seconds)
println(response)
And tests:
import co.freeside.betamax.{TapeMode, Recorder}
import co.freeside.betamax.proxy.jetty.ProxyServer
import dispatch._
import org.scalatest.{Tag, FunSuite}
import scala.concurrent.duration._
import scala.concurrent.{Await, Future}
class StockquoteSpec extends FunSuite with Betamax {
testWithBetamax("stockquote", Some(TapeMode.READ_WRITE))("stockquote") {
val fresponse = service.getQuote(Some("GOOG"))
val response = Await.result(fresponse, 5 seconds)
println(response)
}
}
trait Betamax {
protected def test(testName: String, testTags: Tag*)(testFun: => Unit)
def testWithBetamax(tape: String, mode: Option[TapeMode] = None)(testName: String, testTags: Tag*)(testFun: => Unit) = {
test(testName, testTags: _*) {
val recorder = new Recorder
val proxyServer = new ProxyServer(recorder)
recorder.insertTape(tape)
recorder.getTape.setMode(mode.getOrElse(recorder.getDefaultMode()))
proxyServer.start()
try {
testFun
} finally {
recorder.ejectTape()
proxyServer.stop()
}
}
}
}
Versions:
net.databinder.dispatch 0.11.2
co.freeside.betamax 1.1.2
com.ning.async-http-client 1.8.10
io.netty.netty 3.9.2.Final
It is indeed possible to use proxy with Netty. Although Netty does not read system properties for proxy settings, the settings can be injected using ProxyServerSelector. It is created in build method of AsyncHttpClientConfig:
if (proxyServerSelector == null && useProxySelector) {
proxyServerSelector = ProxyUtils.getJdkDefaultProxyServerSelector();
}
if (proxyServerSelector == null && useProxyProperties) {
proxyServerSelector = ProxyUtils.createProxyServerSelector(System.getProperties());
}
if (proxyServerSelector == null) {
proxyServerSelector = ProxyServerSelector.NO_PROXY_SELECTOR;
}
The only obstacle is that scalaxb uses default config with useProxyProperties=false. You can override it with custom MyDispatchHttpClientsAsync that you can use when creating the service:
val service = (new stockquote.StockQuoteSoap12Bindings with
scalaxb.SoapClientsAsync with
MyDispatchHttpClientsAsync {}).service
And the source code of MyDispatchHttpClientsAsync (the key point is calling setUseProxyProperties(true)):
import com.ning.http.client.providers.netty.NettyAsyncHttpProvider
import com.ning.http.client.{AsyncHttpClientConfig, AsyncHttpClient}
import scalaxb.HttpClientsAsync
/**
* #author miso
*/
trait MyDispatchHttpClientsAsync extends HttpClientsAsync {
lazy val httpClient = new DispatchHttpClient {}
trait DispatchHttpClient extends HttpClient {
import dispatch._, Defaults._
// Keep it lazy. See https://github.com/eed3si9n/scalaxb/pull/279
lazy val http = new Http(new AsyncHttpClient(new NettyAsyncHttpProvider(new AsyncHttpClientConfig.Builder().setUseProxyProperties(true).build())))
// lazy val http = Http.configure(_.setUseProxyProperties(true)) // Maybe later. See https://github.com/eed3si9n/scalaxb/issues/312
def request(in: String, address: java.net.URI, headers: Map[String, String]): concurrent.Future[String] = {
val req = url(address.toString).setBodyEncoding("UTF-8") <:< headers << in
http(req > as.String)
}
}
}
Hey i want to build some small Funsuite test for akka actor application but after combining Testkit with FunSuiteLike i cant call th test anymore.
Somebody an idea why this is happening? is Testkit and funsuite not compatible?
import org.scalatest.{FunSuiteLike, BeforeAndAfterAll}
import akka.testkit.{ImplicitSender, TestKit, TestActorRef}
import akka.actor.{ActorSystem}
class ActorSynchroTest(_system: ActorSystem) extends TestKit(_system) with FunSuiteLike with BeforeAndAfterAll with ImplicitSender{
val actorRef = TestActorRef(new EbTreeDatabase[Int])
val actor = actorRef.underlyingActor
//override def afterAll = TestKit.shutdownActorSystem( system )
test("EbTreeDatabase InsertNewObject is invoked"){
val idList = List(1024L,1025L,1026L,1032L,1033L,1045L,1312L,1800L)
idList.
foreach(x => actorRef ! EbTreeDataObject[Int](x,x,1,None,null))
var cursor:Long = actor.uIdTree.firstKey()
var actorItems:List[Long] = List(cursor)
while(cursor!=actor.uIdTree.lastKey()){
cursor = actor.uIdTree.next(cursor)
cursor :: actorItems
}
assert(idList.diff(actorItems) == List())
}
}
The intelliJ idea test enviroment says:
One or more requested classes are not Suites: model.ActorSynchroTest
class ActorSynchroTest extends TestKit(ActorSystem("ActorSynchroTest",ConfigFactory.parseString(ActorSynchroTest.config)))
with DefaultTimeout with ImplicitSender
with FunSuiteLike with Matchers with BeforeAndAfterAll {
...
}
object ActorSynchroTest {
// Define your test specific configuration here
val config = """
akka {
loglevel = "WARNING"
}
"""
}
Different initialization of the testkit worked in the end before the standard config was used which didn't fit