How to execute action on Play framework startup? - scala

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

Related

Start testcontainers before Playframework starts up

I want to start testcontainers from a docker-compose file (postgres and kafka instance), before the play application (with slick) starts up. I want this, so I can write a end to end test. I can not seem to figure out how this is possible with Play.
import java.io.File
import com.dimafeng.testcontainers.DockerComposeContainer.ComposeFile
import com.dimafeng.testcontainers.{DockerComposeContainer, ForAllTestContainer}
import com.typesafe.config.ConfigFactory
import org.scalatest.{BeforeAndAfterAll, FunSpec}
import org.scalatestplus.play.guice.GuiceFakeApplicationFactory
import play.api.inject.guice.GuiceApplicationBuilder
import play.api.{Application, Configuration, Environment, Mode}
trait TestFunSpec extends FunSpec with BeforeAndAfterAll with GuiceFakeApplicationFactory {
override def fakeApplication(): Application = new GuiceApplicationBuilder()
.in(Environment(new File("."), getClass.getClassLoader, Mode.Test))
.loadConfig(_ => Configuration(ConfigFactory.load("test.conf")))
.build
}
class TestIntegrationSpec extends TestFunSpec with ForAllTestContainer {
override val container = DockerComposeContainer(ComposeFile(Left(new File("docker-compose.yml"))))
it("should test something") {
assert(true)
}
}
Scala version 2.12.10
Testcontainer version 0.35.0
Play slick version 5.0.0
When I execute the test without "TestFunSpec", the docker-compose spins up my services correctly. When I add the play application "TestFunSpec" in scope, the application tries to startup, when doing so, it tries to connect with postgres, which is not yet existing (as the testcontainers are started afterwards).
Thnx in advance.
Update: see answer section for an elaborate answer.
After some deep dive in the Play test suite mechanism, I came up with a workable setup.
Step 1, define your test container suite with an AppProvider:
import com.dimafeng.testcontainers.{Container, ForAllTestContainer}
import org.scalatest.Suite
import org.scalatestplus.play.AppProvider
trait PlayTestContainer extends Suite with AppProvider with ForAllTestContainer {
override val container: Container
}
Step 2, create an abstract PlayTestContainerIntegrationSpec which extends the above trait:
import java.io.File
import com.typesafe.config.ConfigFactory
import org.scalatest.concurrent.{IntegrationPatience, ScalaFutures}
import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach, TestData}
import org.scalatestplus.play.PlaySpec
import org.scalatestplus.play.guice.GuiceOneAppPerTest
import play.api.inject.guice.GuiceApplicationBuilder
import play.api.{Application, Configuration, Environment, Mode}
abstract class PlayTestContainerIntegrationSpec
extends PlaySpec
with PlayTestContainer
with GuiceOneAppPerTest
with ScalaFutures
with IntegrationPatience
with BeforeAndAfterEach
with BeforeAndAfterAll {
override def newAppForTest(testData: TestData): Application = application()
def application(): Application =
new GuiceApplicationBuilder()
.in(Environment(new File("."), getClass.getClassLoader, Mode.Test))
.loadConfig(_ => Configuration(ConfigFactory.load("test.conf")))
.build
}
As you can see, we include the "PlayTestContainer" trait and we override the "newAppForTest" function for the construction of a play application.
Step 3, create a specific integration test, extend the above abstract PlayTestContainerIntegrationSpec, and override the container, to your specific need:
class TestIntegrationSpec extends PlayTestContainerIntegrationSpec {
override val container = DockerComposeContainer(ComposeFile(Left(new File("docker-compose.yml"))))
"should test something" in {
assert(true === true)
}
}
Hope this helps.

Disable singleton when testing with ScalaTest in Play for Scala

I have the following #Singleton in my Play for Scala application that loads on startup:
#Singleton
class Scheduler #Inject()(#Named("mainEtl") mainEtl: ActorRef, system: ActorSystem) {
// some code
}
This is the module where Scheduler is declared. The module is enabled in application.conf:
class Module extends AbstractModule {
def configure() = {
bind(classOf[Scheduler]).asEagerSingleton
}
}
And the related module definition to configure the #Named injected object, also declared in application.conf:
class AkkaBindings extends AbstractModule with AkkaGuiceSupport {
def configure = {
bindActor[MainEtl]("mainEtl")
}
}
When I run any ScalaTest test, apparently the singleton starts running because I get an error saying that it doesn't find MainEtl (the object injected in the Scheduler class). The point is that I don't need to run the singleton for my tests, so I need to disable it.
This is how I invoke the Play application in my tests:
class ManageBanksTest extends PlaySpec with OneAppPerSuite with MockitoSugar {
implicit override lazy val app = new GuiceApplicationBuilder().build
// more test code
}
This is how I tried to disable it, but it doesn't work as I get the same error:
implicit override lazy val app = new GuiceApplicationBuilder()
.disable[Scheduler]
.build
Alternatively, I could mock Scheduler, but I would have to mock also the #Named injected object and I couldn't find information on how to achieve that.
Any ideas?
This is the solution: to disable the Module class not to declare Scheduler as singleton:
implicit override lazy val app = new GuiceApplicationBuilder()
.disable[Module]
.build
import com.google.inject.AbstractModule
import com.google.inject.name.Names
import org.specs2.mock.Mockito
import play.api.inject.guice.{GuiceApplicationBuilder, GuiceableModule}
val modules = Option(new AbstractModule {
override def configure() = {
val mockMainETL = mock[MainEtl]
bind(classOf[ActorRef])
.annotatedWith(Names.named("mainEtl"))
.toInstance(mockMainETL)
val mock1 = mock[ManageBanksDAO]
mock1.readMany answers { _ => Future{seqMany}}
val mockManageBanks = mock[ManageBanks]
bind(classOf[ManageBanks]).toInstance(new ManageBanks(mock1))
}
})
lazy val app = new GuiceApplicationBuilder()
.overrides(modules.map(GuiceableModule.guiceable).toSeq: _*)
.build
Try configuring your mock inside modules and add those modules while initializing your application. This will inject custom mocks.
Also instead of this :
val controller = new ManageBanks(mock1)
Try this:
val controller = app.injector.instanceOf(classOf[ManageBanks])

Play! Scala: How to disable actor modules loaded at the start of the application when testing?

I load some actors at the beginning of my Play! 2.5 application like this:
class Module extends AbstractModule with AkkaGuiceSupport with ScalaModule {
override def configure() = {
bindActor[MainSupervisor]("main-supervisor")
}
}
The problem is that when I run my tests I got a lot a logs (and unnecessary calls) from the loaded actor (and the entire cluster and remote system) like
[INFO ] a.r.Remoting: Starting remoting
[INFO ] a.r.Remoting: Remoting started; listening on addresses :[akka.tcp://application#127.0.0.1:41496]
[INFO ] a.r.Remoting: Remoting now listens on addresses: [akka.tcp://application#127.0.0.1:41496]
I have, for instance, a class that I test where I don't need any actor, but I don't find any way to disable them (or even better the entire actor system).
What I have tried is:
lazy val appWithoutActorsBuilder = new GuiceApplicationBuilder()
.disable[ActorSystem]
.disable[MainSupervisor]
.build()
lazy val injectorWithoutActors = appWithoutActorsBuilder.injector
lazy val wSClientWithoutActors = injectorWithoutActors.instanceOf[WSClient]
lazy val ec = scala.concurrent.ExecutionContext.Implicits.global
lazy val facebookAPI = new FacebookAPI(wSClientWithoutActors, ec)
But when I test the FacebookAPI methods (e.g. facebookAPI.method(...) mustBe ...) I still see the logs from Akka. What can I do in order to avoid it?
You have to disable your module instead of your actor. So, considering that you have this class:
package com.acme.modules
class Module extends AbstractModule with AkkaGuiceSupport with ScalaModule {
override def configure() = {
bindActor[MainSupervisor]("main-supervisor")
}
}
And that you are registering it at your conf/application.conf like this:
play.modules.enabled += "com.acme.modules.Module"
Then, at your tests, your have to do the following:
lazy val appWithoutActorsBuilder = new GuiceApplicationBuilder()
.disable[com.acme.modules.Module]
.build()

Scala Play 2.4 run akka scheduler service on Application start

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 =)

How to define onStart method in scala play framework 2.4

how can i define startup job in play framework 2.4 with scala?
play framework GlobalSetting
I have already:
class StartupConfigurationModule extends AbstractModule{
override def configure(): Unit = {
Akka.system.scheduler.schedule(Duration(0,duration.HOURS),Duration(24,duration.HOURS))(Id3Service.start())
Akka.system.dispatcher
}
}
You need to register this in the modules.enabled of your application (in application.conf).
It should schedule a call to start on the Id3Service after 0 hours and then every 24 hours.
The issue is that the module doesn't declare a dependency on a running application, or more interestingly on a started actorSystem. Guice can decide to start it before the app is initialized.
The follwing is one way to force the dependency on the initialized actorSystem (and reduce the footprint of your dependency)
import javax.inject.{ Singleton, Inject }
import akka.actor.ActorSystem
import com.google.inject.AbstractModule
import scala.concurrent.duration._
class StartupConfigurationModule extends AbstractModule {
override def configure(): Unit = {
bind(classOf[Schedule]).asEagerSingleton()
}
}
#Singleton
class Schedule #Inject() (actorSystem: ActorSystem) {
implicit val ec = actorSystem.dispatcher
actorSystem.scheduler.schedule(Duration(0, HOURS), Duration(24, HOURS))(Id3Service.start())
}
object Id3Service {
def start(): Unit = println("started")
}