scalaplayframeworkplayframework-2.6

How to get hold of the Default BodyParser?


What I'm asking is how to get hold of the Default Scala Play BodyParser implementation in 2.6. However, if you know a cleaner way to solve this use-case I'm more than happy to hear it.

Some preamble ... I reused a Java Play framework Play-Authenticate (PA) to build a Scala Play application on top. Yes crucify me for it! I wanted all the social authentication functionality but have my application in Scala and use e.g. Slick. Yes yes I could try rewriting PA in Scala but no time now ... thinking about it though.

Now that this is out of the way, here is my use case. I have Scala controllers and need to pass Java Context to the PA framework to be able to use it. I also need to check whether the user can be cookie-authenticated, namely whether the user checked the "Remember Me" box when she logged the first time. This "solution" used to work before migrating to Play 2.6 but now doesn't work because of the BodyParser.

This is what the "burger" looks like. I want to eat it so ... I need to authenticate the user by checking the incoming cookie but while at it a Java Context is created (to interoperate with PA). But since I have the dreaded Java Context I can make it available throughout the Scala Application in the scope of one request:

def index =
  TryCookieAuthAction { implicit jContext =>  // <==== this is the burger
    deadbolt.WithAuthRequest()() { implicit request =>
     Future {
        Ok(indexView(userService))
     }
  }
}

Now let's look at the cow. Pre 2.6 it used to work because I wasn't forced to override parser but now in 2.6 I'm forced to:

case class TryCookieAuthAction[A](block: Http.Context => Action[A])(implicit auth: PlayAuthenticate, config: Configuration, env: Environment, mat: Materializer, ec: ExecutionContext) extends Action[A] {
  def apply(request: Request[A]): Future[Result] = {
    val contextComponents = JavaHelpers.createContextComponents(config, env)
    val jContext = JavaHelpers.createJavaContext(request, contextComponents)

    TryCookieAuthAction.jContextDv += (request.id -> jContext)

    if(!auth.isLoggedIn(jContext)) {
      // calling Java here so need a Java Context
      auth.tryAuthenticateWithCookie(jContext)
    }

    val scalaResult: Future[Result] = Await.ready(block(jContext)(request), 60 seconds)

    val session : Seq[(String, String)] = jContext.session().keySet().toArray.map(key => (key.toString, jContext.session().get(key)))
    val cookies : Seq[Cookie] = jContext.response().cookies().asScala.toSeq.map(cookie =>
      Cookie(cookie.name(), cookie.value(), maxAge = Option(cookie.maxAge()), path = cookie.path(), domain = Option(cookie.domain()),
        secure = cookie.secure(), httpOnly = cookie.httpOnly())
    )

    TryCookieAuthAction.jContextDv -= request.id

    scalaResult.map(_.withSession(session : _*).withCookies(cookies : _*))
  }

  override def executionContext = ec

  override val parser: BodyParser[A] = ???
}

object TryCookieAuthAction {
  private lazy val jContextDv = TrieMap[Long, play.mvc.Http.Context]()

  /**
    * Extracts the Java context given a request
    * @param request The request
    * @tparam A The request body type
    */
  implicit class RequestToContext[A](request: Request[A]) {
    def jContextOption : Option[Http.Context] = jContextDv.get(request.id)
    def jContext : Http.Context = jContextDv(request.id)
  }

  def apply[A](action: Action[A])(implicit auth: PlayAuthenticate, config: Configuration, env: Environment, mat: Materializer, ec: ExecutionContext): TryCookieAuthAction[A] = TryCookieAuthAction(_ => action)
}

My questions is how to get the Default BodyParser or is there a more clever way to do this? For example, creating an ActionBuilder I tried but then I can't make jContext implicitly available for the enclosed Action.


Solution

  • I found a solution or potentially the solution?

    First by changing the object WithJContextSupportAction's apply method to include play.api.mvc.PlayBodyParsers as implicit parameter:

    def apply[A](action: Action[A])(implicit config: Configuration, env: Environment, bodyParsers: PlayBodyParsers, ec: ExecutionContext): WithJContextSupportAction[A] = WithJContextSupportAction(_ => action)
    

    Then propagate the new implicit change to the action class:

    case class WithJContextSupportAction[A](block: JContext => Action[A])(implicit config: Configuration, env: Environment,
                                                                          bodyParsers: PlayBodyParsers, ec: ExecutionContext) extends Action[A] {
    

    Finally overriding the parser like so:

    override def parser= bodyParsers.default.asInstanceOf[BodyParser[A]]