Reputation: 917
I have an action I want to apply to multiple routes in my Play application. These routes perform actions on a product, and a product can have various versions. I want my API to work such that the user can specify a version explicitly (via a query parameter), and if they do not specify one we will look up the latest version from the DB for them and operate on that one. So this action needs to be able to look up the latest version of a product, but we need to know which product is being asked for. In the route's controller, this is obvious. Play calls the route controller with route parameters as arguments:
@RequireProductVersion()
public CompletionStage<Result> getProduct(String productId) {
...
}
But in our action, we only have this Play internal Context
object to work with. My action looks something like this:
public class RequireProductVersion extends Action<RequireProductVersion> {
@Override
public CompletionStage<Result> call(Http.Context ctx) {
final String version = ctx.request().getQueryString("version");
// if an explicit "version" parameter was specified, verify it and use it
if (version != null) {
...
} else {
// look up the latest version for this product
final String productId = ctx.request.????getParameter("productId");
return lookupLatestProductVersion(productId).thenCompose( ... );
}
}
}
Although I have some additional validity checking in that action. Sometimes I return an error from there immediately. So we could replace this action composition solution by adding the query string parameter "version" to all the routes and adding a half dozen lines of code in each of my route controllers:
@RequireProductVersion()
public CompletionStage<Result> getProduct(String productId, @Nullable String productVersion) {
final int productVersion;
try {
productVersion = Utils.getProductVersion(productId, productVersion);
} catch (ProductVersionException e) {
return CompletableFuture.completedFuture(e.getAppropriateResult());
}
...
}
But this use case is exactly what action composition should be for, I think. It just seems that the route parameters are missing. The Context
object exposed in the Action call() method has a lot of stuff in it, actually. Headers are there, query parameters are there, and even the exact path being hit is there! Even if that were not so, by the point the framework has parsed the route and determined the values of the route parameters. This must be true because if it was not, then how would it know which action to call? However, it appears that these parsed parameters are completely unavailable to us. We could parse them again ourselves from the path. But why should we have to do that? We would be parsing the path twice. Why doesn't the framework expose these values?
There's an interesting article I found that, to solve a similar problem, suggests a hack that will put a url parameter into the query string parameters map. https://alots.wordpress.com/2014/05/01/accessing-url-parameters-as-get-parameters-in-play/ However it appears to me that this method is also basically double parsing the path anyway, although I might be misinterpreting it as I'm not very familiar with Scala. If so, I might as well just hack in logic to reparse the path in my Action.
Upvotes: 4
Views: 1184
Reputation: 917
Okay so this problem is solvable in Scala. It does not appear there is currently any way to solve it in Java due to how Play Java uses annotations for action composition (and for body parsers, which is another place I ran into this exact same problem). You would have to parse the path again yourself. However, it looks like it is quite easy to accomplish in Scala. I haven’t tested either of these and I’m not very familiar with Scala, but it looks like for Play in Scala, action composition works differently.
this gist has an example of how Play Scala action composition should support this: https://gist.github.com/wolfendale/75e8b5e9a7ace95aa7e6d123e6c6dacd
jroper’s posts in this issue thread also demonstrate what appears to me to be the same solution: https://github.com/playframework/playframework/issues/3378#issuecomment-54925034
If those work, the technique demonstrated in the article I linked in the original post is not at all necessary to solve this problem if you are using Scala. However, because it only requires writing some code in Scala (whereas for the technique demonstrated by wolfendale and jroper, you need to write your controller in Scala as well), it could be a useful way to solve this problem and still write most of your application in Java. I don’t know for sure. I haven’t tested it and I’m not interested in that kind of hack.
Upvotes: 3
Reputation: 647
You can't, at least, not out of the box. Play doesn't provide ways to get the request params in action composition.
Basically : You have to parse yourself.
Upvotes: 1