1

I have a code in my Play Scala (2.5x, 2.11.11) app which has been running just fine so far (it is based on the following link: https://fizzylogic.nl/2016/11/27/authorize-access-to-your-play-application-using-action-builders-and-action-functions/). But now I need to pass another class instance to ApplicationAuthorizationHandler class (NOTE: throughout my code I am using Guice DI for injecting parameters into class constructors).

Current code:

class ApplicationAuthorizationHandler
   extends AuthorizationHandler {
...
}

trait AuthorizationHandler {
...
}

trait AuthorizationCheck {
   def authorizationHandler: AuthorizationHandler = new ApplicationAuthorizationHandler

   object AuthenticatedAction extends ActionBuilder[RequestWithPrincipal] {
      override def invokeBlock[A](request: Request[A], block: (RequestWithPrincipal[A]) => Future[Result]): Future[Result] = {
         def unauthorizedAction = authorizationHandler.unauthorized(RequestWithOptionalPrincipal(None, request))
         def authorizedAction(principal: Principal) = block(RequestWithPrincipal(principal, request))

         authorizationHandler.principal(request).fold(unauthorizedAction)(authorizedAction)
      }
  }
}

//Example controller using this trait AuthorizationCheck
class MyController @Inject() extends Controller with AuthorizationCheck {
    def myAction = AuthenticatedAction { implicit request =>
...
}

Desired code:

class ApplicationAuthorizationHandler @Inject() (userService: UserService)
   extends AuthorizationHandler {
   ...
   // userService is used here
}

But since the instance of ApplicationAuthorizationHandler is instantiated inside trait AuthorizationCheck I can't inject UserService instance into it. I am Mixin this trait with all controllers so would like to keep the same way unless there is a better way (and there must be). First, is there a way to inject directly into class/trait method ? Alternatively, is there a way where I don't instantiate ApplicationAuthorizationHandler in trait AuthorizationCheck and pass it during run-time inside the controller ? Or any other way ?

NKM
  • 602
  • 1
  • 6
  • 18
  • 1
    Have you read the Play [documentation](https://www.playframework.com/documentation/2.6.x/ScalaDependencyInjection) about DI first? – cchantep Feb 28 '18 at 16:53

2 Answers2

2

A trait does not need to provide an implementation, so you can have something like:

trait AuthorizationHandler {
  ...
}

class ApplicationAuthorizationHandler extends AuthorizationHandler {
  ...
}

trait AuthorizationCheck {

  // just declaring that implementations needs to provide a 
  def authorizationHandler: AuthorizationHandler 

  object AuthenticatedAction extends ActionBuilder[RequestWithPrincipal] {
    override def invokeBlock[A](request: Request[A], block: (RequestWithPrincipal[A]) => Future[Result]): Future[Result] = {
      def unauthorizedAction = authorizationHandler.unauthorized(RequestWithOptionalPrincipal(None, request))
      def authorizedAction(principal: Principal) = block(RequestWithPrincipal(principal, request))

      authorizationHandler.principal(request).fold(unauthorizedAction)(authorizedAction)
    }
  }
}

// So, now this controller needs to provide a concrete implementation 
// of "authorizationHandler" as declared by "AuthorizationCheck".
// You can do it by injecting a "AuthorizationHandler" as a val with
// name authorizationHandler.
class MyController @Inject()(val authorizationHandler: AuthorizationHandler) extends Controller with AuthorizationCheck {

   def myAction = AuthenticatedAction { implicit request =>
     ...
   }
}

And of course, you need to provide a module to bind AuthorizationHandler to ApplicationAuthorizationHandler:

import play.api.inject._

class AuthorizationHandlerModule extends SimpleModule(
  bind[AuthorizationHandler].to[ApplicationAuthorizationHandler]
)

Of course, ApplicationAuthorizationHandler can have its own dependencies injected. You can see more details at our docs.

marcospereira
  • 12,045
  • 3
  • 46
  • 52
  • Your suggested solution works. Since the original solution on the referred link had similar code but what was missing is the `val` prefix to the injected `authorizationHandler: AuthorizationHandler` parameter. Without it gives compile error: `class MyController needs to be abstract, since method authorizationHandler in trait AuthorizationCheck of type => controllers.authapi.AuthorizationHandler is not defined [error] class MyController @Inject() (authorizationHandler: AuthorizationHandler) extends Controller with AuthorizationCheck {` Thanks a lot. – NKM Mar 02 '18 at 08:21
  • Further: I wish there is some documentation which can be referred on such type of Scala nuances. I tried to search in the books as well as web but couldn't find any thing which could point me in the right direction. This is a simple problem but I struggled on it for a couple of days. I almost took the other approach of converting trait into a class so that I could explicitly inject parameter to it. But then it would have forced all controllers to be injected with that new class and access its members via `.` notation. – NKM Mar 02 '18 at 08:30
1

There are many cases when you cannot use the @Inject approach of guice. This is true when dependencies are needed inside of trait and also actors.

The approach I use in these cases is that I put my injector in a object

object Injector {
  val injector = Guice.createInjector(new ProjectModule())
}

since the above is inside of an object, you can access it from anywhere. (its like a singleton).

Now inside your trait or an actor when you need the user service do

trait Foo {
   lazy val userService = Injector.injector.getInstance(classOf[UserService])
}

Don't forget to make the variable lazy, because you want the instance to be created as late as possible when the injector has already been created.

Knows Not Much
  • 30,395
  • 60
  • 197
  • 373
  • Thanks for the suggestion. I tried as you suggested but then I get following type of errors: Caused by: java.lang.ExceptionInInitializerError: null at controllers.authapi.AuthorizationCheck$class.userService(AuthorizationCheck.scala:19) at controllers.MyController.userService$lzycompute(MyController.scala:10) at controllers.MyController.userService(MyController.scala:10) at controllers.authapi.AuthorizationCheck$class.authorizationHandler(AuthorizationCheck.scala:20) ... Caused by: com.google.inject.CreationException: Unable to create injector, see the following errors: – NKM Mar 01 '18 at 03:17
  • The module which was used to create the injector must have the bind for the UserService. – Knows Not Much Mar 01 '18 at 03:28
  • My Module.scala class has this binding in configure(): bind[IdentityService[User]].to[UserService] That is the only place where UserService is bound and I have been using UserService instance via Inject throughout my code. Only here when i use the Injector as above I get these errors. – NKM Mar 01 '18 at 04:10
  • Looking at the error I did a bind of the dependency: bind[DBApi].to[DefaultDBApi] bind[IdentityService[User]].to[UserService] But now I get this error: Caused by: com.google.inject.CreationException: Unable to create injector, see the following errors: 1) Could not find a suitable constructor in play.api.db.DefaultDBApi. Classes must have either one (and only one) constructor annotated with @Inject or a zero-argument constructor that is not private. at play.api.db.DefaultDBApi.class(DefaultDBApi.scala:15) at utils.silhouette.AuthModule.bind(AuthModule.scala:32) – NKM Mar 01 '18 at 05:38
  • The answer above by @marcospereira mentioned correct solution. On the other hand I learned many things about DI with your comments: "Knows Not Much" and implemented those as well. Thanks for your help. – NKM Mar 02 '18 at 08:34