Accessing url parameters as get parameters in Play

A few weeks ago, I published here a post explaining my approach to implement a multi tenant strategy in my application. My main problem was that I did not have access to the url parameters in order to extract the tenant id. Play does not provide any easy way to access this information. So I kept searching and I found one solution, not to pretty, but functional! Just to remember, here I have my action.

	public class TenantResourceAction extends Action{

		@Override
		public Promise call(Context ctx) throws Throwable {
			Long id = Long.parseLong(ctx.request().
					  getQueryString("clientId"));
			//do your stuff here
			return delegate.call(ctx);
		}

	}

The getQueryString(“clientd”) method does not return the value because, as explained here, Play’s team decided do not insert url parameters into the querystring parameters map. So I need to extract the client id from almost every url. For example, http://localhost:9000/user/3/photos. It’s mandatory to check if the current logged user owns the requested resource. To solve this issue was necessary to use some classes from Play core, what was nice to learn more about the tool that I use a lot :). The first part of the job was to extend GlobalSettings and override the Play’s default behavior.

	import play.GlobalSettings
	import play.api.{ GlobalSettings => ScalaGlobalSettings }
	import play.api.mvc.RequestHeader
	import play.api.mvc.Handler

	object Global extends GlobalSettings with ScalaGlobalSettings {

	  //overrided because it was generating a conflict
	  override def getControllerInstance[A](controllerClass: Class[A]) = controllerClass.newInstance();

	  override def onRequestReceived(request: RequestHeader): (RequestHeader, Handler) = {
	    val (rh, handler) = super.onRequestReceived(request)
	    val route = rh.tags("ROUTE_PATTERN")
		...
	  }

	}

Here I had too choose Scala because was necessary the usage of the tags method from Scala version of RequestHeader. This method contains some meta information created by Play during the processing of parsing a request. This is the case of ROUTE_PATTERN key. This key returns the configure value from your routes file that was used to handle the current request. Now that we have the route, for a given uri we have to be able to retrieve all dynamic and static part from this uri. This is the hardest part, because we have to deal with some hidden parts of Play :). When you download the source code of the project, you will see that there are a several projects. One of them is the Routes-Compiler, which is responsible for create the object that represents a parsed route. The class of this object is play.router.PathPattern.  Take a look at the code needed to build this object.

	package play.router

	import play.router.RoutesCompiler._

	object RoutesCompilerHack {

		lazy val parser = new RouteFileParser

		def transform(verb:String,path:String):PathPattern =
				parser.parse(s"$verb $path  fakepackage.fakecontroller.fakemethod")
				.get.head.asInstanceOf[Route].path
	}

In order to have this code working, I had to put it in a package called play.router. The RouteFileParser is a private package class and can’t be accessed outside. Essentially, it is hack :(. After that, is necessary to convert the current PathPattern object to a play.core.PathPattern, which has a method that receives a uri and returns the bound parts with their respective values.

	import play.router.RoutesCompilerHack
	import play.core.{ PathPattern => PlayCorePathPattern }
	import play.router.PathPart
	import play.router.DynamicPart
	import play.router.StaticPart
	import play.core.{DynamicPart => CoreDynamicPart}
	import play.core.{StaticPart => CoreStaticPart}

	object TentantIdExtractor {

	  def apply(confRoute:String,uri:String):String = {
            //here could be used some cache 🙂
	    val pathPattern:play.router.PathPattern	 = RoutesCompilerHack.transform("GET", confRoute)

		//Dynamic and Static objects used inside the Router compiler project
	    val routerCompilerParts = pathPattern.parts

		//Transforming them to play.core.Part(Dynamic and Static)
	    val coreParts = routerCompilerParts.map { part =>
	      part match {
	        case DynamicPart(name, constraint, _) => CoreDynamicPart(name,constraint,true)
	        case StaticPart(name) => {
	          //the first slash("/") was not being generated
	          if(name.startsWith("/")) CoreStaticPart(name) else CoreStaticPart(s"/$name")
	        }
	      }
	    }

		//build the play.core.PathPattern
	    val corePathPattern = PlayCorePathPattern(coreParts)

The objects DynamicPart and StaticPart are used internally in Play to represent the url parts. Now, that we built the play.core.PathPattern object, it is possible to query this object with a requested uri and to retrieve a Option object with the values.

     val result:Option[Map[String,Either[Throwable,String]]] = corePathPattern(uri)

This monster object, that could be encapsulated in some class, has all the dynamic values passed by client. Now we can search for some variable and get the value!

	result.get("clientId") match {
	    case Right(value) => value
	    case Left(e) => throw e
	}

To finish our job, we now can get this value and push into query parameters!

	override def onRequestReceived(request: RequestHeader): (RequestHeader, Handler) = {
	  val (rh, handler) = super.onRequestReceived(request)
	  val route = rh.tags("ROUTE_PATTERN")
	  val tenantId = TentantIdExtractor(route,request.uri)
	  val newParams = rh.queryString.+("clientId" -> List(tenantId))

	  val newRequestHeader = rh.copy(rh.id,rh.tags,rh.uri,rh.path,rh.method,rh.version,newParams,rh.headers,rh.remoteAddress)
	 (newRequestHeader, handler)
	}

We pushed the id and rebuilt the RequestHeader to make this new parameter available to be accessed through request.getQueryString(“clientId”). The code is available here and you can use in your multi tenants projects! That is it folks. I hope this has helped you to understand a little bit more about Play internals and how important is to know the tool that you are using. And please, if there is an easier way, let me know. I enjoyed to hack the framework but I prefer an easier code 🙂

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s