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 ?

1
  • 1
    Have you read the Play documentation about DI first? Commented Feb 28, 2018 at 16:53

2 Answers 2

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.

Sign up to request clarification or add additional context in comments.

2 Comments

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.
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.
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.

5 Comments

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:
The module which was used to create the injector must have the bind for the UserService.
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.
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)
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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.