Skip to content

Commit

Permalink
Add initial module for request-scoped objects
Browse files Browse the repository at this point in the history
The work is mostly based in the code from this pull request
on play-framework project [1].

[1] - playframework/playframework#6798
  • Loading branch information
AlexITC committed Sep 10, 2017
1 parent b7b8836 commit db46e95
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 0 deletions.
10 changes: 10 additions & 0 deletions build.sbt
@@ -0,0 +1,10 @@
name := "play-request-tracer"

organization := "com.alexitc"

scalaVersion := "2.12.2"

val playVersion = "2.6.3"

libraryDependencies += "com.typesafe.play" % "play_2.12" % playVersion
libraryDependencies += "com.google.inject" % "guice" % "4.1.0"
1 change: 1 addition & 0 deletions project/build.properties
@@ -0,0 +1 @@
sbt.version=0.13.16
@@ -0,0 +1,89 @@
package com.alexitc.play.api.inject.guice

import com.google.inject.{AbstractModule, Injector, Module}
import play.api.mvc.RequestHeader
import play.api.routing.Router

import scala.reflect.ClassTag

/**
* A router that creates a new instance of a router from the injector on every request, also the request-scoped
* dependencies defined in the provided modules.
*
* To use, extend this class:
*
* {{{
* class MyRequestScopedRouter @Inject()(injector: Injector)
* extends RequestScopedRouter[router.Routes](injector, modules = Seq(new MyRequestModule))
* }}}
*
* Then add the router to your configuration, e.g. "play.http.router = com.example.MyRequestScopedRouter"
*/
class RequestScopedRouter[T <: Router](
injector: Injector,
prefix: String = "/",
modules: Seq[_ >: AbstractModule] = Seq.empty)(
implicit routerClassTag: ClassTag[T])
extends Router
with RequestScopedRouterProvider {

def this(injector: Injector, routerClass: Class[T], prefix: String, modules: Array[Module]) {
this(injector, prefix, modules)(ClassTag(routerClass))
}

/**
* Provide a router that is scoped to this request.
*/
def routerForRequest(request: RequestHeader): Router = {
val injectorWithRequest = injector
.createChildInjector(new RequestScopedModule(request))

val requestScopedModules = modules.map { clazz => injectorWithRequest.getInstance(clazz.asInstanceOf[Class[AbstractModule]]) }

val childInjector = injectorWithRequest
.createChildInjector(requestScopedModules: _*)

childInjector
.getInstance(routerClassTag.runtimeClass)
.asInstanceOf[T]
.withPrefix(prefix)
}

def routes: Router.Routes = Function.unlift { request =>
val requestScopedRouter = routerForRequest(request)
requestScopedRouter.routes.lift(request)
}

def withPrefix(prefix: String): RequestScopedRouter[T] = new RequestScopedRouter(injector, prefix, modules)

/**
* Since we don't have a request yet, we can't get the documentation, so this just returns an empty Seq. To get the
* route documentation, call routerForRequest(request).documentation
*
* @return A list of method, path pattern and controller/method invocations for each route.
*/
def documentation: Seq[(String, String, String)] = Seq.empty
}

/**
* A provider that returns a router scoped to a specific request. Useful when you want to access the router's
* documentation (which is not available from RequestScopedRouter).
*/
trait RequestScopedRouterProvider {

/**
* Provide a router that is scoped to this request.
*/
def routerForRequest(request: RequestHeader): Router
}

class RequestScopedModule(request: RequestHeader) extends AbstractModule {

override def configure(): Unit = {
bind(classOf[RequestHeader])
.toInstance(request)

bind(classOf[play.mvc.Http.RequestHeader])
.toInstance(new play.core.j.RequestHeaderImpl(request))
}
}
2 changes: 2 additions & 0 deletions version.sbt
@@ -0,0 +1,2 @@
version in ThisBuild := "0.1.0"

0 comments on commit db46e95

Please sign in to comment.