Play 2.1 Addendum

In my previous post, Play 2.1: The Bloom Is Off The Rose, I talked about several real-world problems I’ve had when using Play 2.1. I’ve since received responses from the Play team which prompted me to continue trying to find solutions to my problems. The outcome is a mixed bag.

Almost Immutable Everything

In my previous post I complained about not being able to find a way to wrap/decorate the Request object. This is still a problem, but James Roper (on the Play team) gave me some suggestions. First he suggested that I create a custom BodyParser and second he suggested that I put things on the Context map. Only one of these worked and it was not very elegant. Below is my attempt at creating a custom Bodyparser, using his recommendations.

public class MyBodyParser implements play.mvc.BodyParser { @Override public play.api.mvc.BodyParser<play.mvc.Http.RequestBody> parser(int maxlength) { play.mvc.BodyParser.Json existingJson = new play.mvc.BodyParser.Json(); play.api.mvc.BodyParser parser = existingJson.parser(maxlength).map(new akka.dispatch.Mapper<play.mvc.Http.RequestBody, Object>() { @Override public Object apply(play.mvc.Http.RequestBody requestBody) { // play.libs.Json.fromJson(requestBody.asJson(), ??class??); // <– can’t get class from here return super.apply(requestBody); } }); return parser; } }

I really thought this was going to be a great solution until I tried to do the actual object de-serialization. Jackson Mapper can’t de-serialize Json into an object unless it knows what class the object should be. As far as I can tell, there’s no way to get that information from within the BodyParser, so I went to James’ next recommendation: Using Context.args.

The Context class has a map called “args” that can be used similarly to ServletRequest.attribute. While it’s not very elegant, it gets the job done.

@With(JsonParsingAction.class) @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface FromJsonTo { Class<?> value() default Object.class; }     public class JsonParsingAction extends Action<FromJsonTo> { @Override public Result call(Http.Context ctx) throws Throwable { Class<?> clazz = configuration.value(); Object jsonObj = Json.fromJson(ctx.request().body().asJson(), clazz); ctx.args.put(“jsonObject”, jsonObj); return delegate.call(ctx); } }     public class BaseController extends Controller { protected static <T> T bodyAsJson(Class<T> t) { return t.cast(ctx().args.get(“jsonObject”)); } }     public class Application extends BaseController { @FromJsonTo(Email.class) public static Result sendEmail() { Email email = bodyAsJson(Email.class); // send email } }

Above I’ve created a custom @With annotation to be used with my custom Action. The annotation takes a Class, so that my Action will know what class to use to de-serialize the JSON. My Action de-serializes the JSON and places the object onto the args map. Because args is a Map<String,Object>, every controller method that wants to use my Action would have to cast, so I’ve created a convenience method in BaseController. Frankly, I consider this a hack, but so far it is the only way I have found to change anything about any of the play.mvc.Http objects (ContextRequestResponse, etc).

Extending play.mvc.Http

Being fairly persistent, I decided to try extending Http.Request and Http.Context and then wrapping the actual request and context in an Action, but that was another dead end.

public class MyRequest extends Http.Request{ Http.Request wrappedRequest;   public MyRequest(Http.Request wrappedRequest) { this.wrappedRequest = wrappedRequest; }   @Override public Http.RequestBody body() { return new Http.RequestBody() { public <T> T as(java.lang.Class<T> tClass) { return play.libs.Json.fromJson(wrappedRequest.body().asJson(), tClass); } }; }   @Override public String uri() { return wrappedRequest.uri(); }   @Override public String method() { return wrappedRequest.uri(); }   @Override public String version() { return wrappedRequest.uri(); }   @Override public String remoteAddress() { return wrappedRequest.uri(); }   @Override public String host() { return wrappedRequest.uri(); }   @Override public String path() { return wrappedRequest.uri(); }   @Override public List<Lang> acceptLanguages() { return wrappedRequest.acceptLanguages(); }   @Override public List<String> accept() { return wrappedRequest.accept(); }   @Override public List<MediaRange> acceptedTypes() { return wrappedRequest.acceptedTypes(); }   @Override public boolean accepts(String s) { return wrappedRequest.accepts(s); }   @Override public Map<String, String[]> queryString() { return wrappedRequest.queryString(); }   @Override public Http.Cookies cookies() { return wrappedRequest.cookies(); }   @Override public Map<String, String[]> headers() { return wrappedRequest.headers(); } }

Here I’ve overridden the body() method and return an anonymous RequestBody in which I’ve overridden the as() method. The as() method is where I’m doing the JSON de-Serialization. Since I wanted to do this using action composition, I also extended Http.Context for convenience.

public class MyContext extends Http.Context { public MyContext(Http.Context ctx) { super(ctx.id(), ctx._requestHeader(),new MyRequest(ctx.request()),ctx.session(),ctx.flash(),ctx.args); } }

All I’m doing here is calling the Context constructor and passing in my MyRequest object wrapping the actual Request. Next I modified the JsonParsingAction Action used in the example above.

public class JsonParsingAction extends Action<FromJsonTo> {   @Override public Result call(Http.Context ctx) throws Throwable { Class<?> clazz = configuration.value(); Json.fromJson(ctx.request().body().asJson(), clazz); MyContext context = new MyContext(ctx); return delegate.call(context); } }

Instead of adding the de-serialized object to the context.args map, I’ve created a MyContext object wrapping the actual context and then passing that to delegate.call(). I assumed that my controller would get my version of the context and be able to use the as() method to get the object off of the request body, but I was wrong.

@FromJsonTo(Email.class) public static Result testJsonInject() { Email email = request().body().as(Email.class); … }

in this code, the request().body().as(Email.class) returns null. I debugged the app to see what was happening and I found that request().body() returned an instance of play.core.j.JavaParsers$DefaultRequestBody, rather than MyRequest. Similarly ctx() did not return a MyContext instance either. So apparently action composition does not let you wrap or override the Http.Context object. I also tried something similar in Global.onRequest(Http.Request request, Method actionMethod) – same result. So, once again I’m asking myself why would there be a way to intercept a controller method (Action composition) but no way to change the Request/Result object from there?

The Good Bits

I did say the outcome was mixed, didn’t I? It seems I owe you some good news. I do like a happy ending, if I can get one, but I’ll take a non-tragic ending. To start with, there is a way to de-serialize JSON from a request.

Edit: You may have issues if you are binding to a List or other complex objects.

public static Result sendEmail() { Form<Email> emailForm = Form.form(Email.class); Email email = emailForm.bindFromRequest().get(); … }

I’ve been aware of this method for some time, but de-serializing JSON was never really the point. Before I discovered this method I just had toJSON() and fromJSON() convenience methods in a BaseController class. The point was to discover how one would go about wrapping/decorating incoming requests and outgoing responses. As far as I can tell, I can’t do that. So this meets our needs until I have another scenario not involving JSON serialization.

The second good bit of news is that I found a work-around for serializing an outgoing object into JSON without using any kind of interception technique. I just extended play.mvc.Content.

public class JsonContent implements play.mvc.Content {   private String content;   public JsonContent(Object obj) {     this.content = Json.stringify(Json.toJson(obj));   }     @Override   public String body() {     return content;   }     @Override   public String contentType() {     return “application/json”;   } }

To use this I simply create one inline and pass it to my Result builder.

public Result getEmail(int id) { Email email = dao.findEmailById(id); return ok(new JsonContent(email)); }

It doesn’t solve the original problem, but it works.

Take Away

I didn’t address some of the other issues from my last post, including the immutable Configuration object. I dismissed the configuration suggestions in the comments of the last post and explained why they were not suitable for my client. Also, the author of Play2War made some comments and I’ve since created some Github issues. He has been very responsive to help requests. Everything else is either not that important, or something that isn’t going to change. So I’m just a bit more optimistic than last time, but mostly still in the same boat. Would I be having these problems if my team learned Scala? Who knows.

Scroll to Top