Play Framework - Making a java webframework looking less Java

Alexander Reelsen

Play Framework

A look inside the machine room

Alexander Reelsen
alexander@reelsen.net
@spinscale

Inside the machine room

  • Model:
    • Property Enhancement
    • Finders
  • Controller:
    • Redirects
    • Rendering context

About me - Alexander Reelsen

  • Studied information systems
  • 10 years linux system engineering, converted to software engineering
  • Web framework enthusiast, fed up with complex java environment for simple webapps
  • Other interests: Scaling web architectures, Web 2.0 (nosql, search)
  • Started with play 1.0 in 2009, started writing cookbook in dec 2010
  • Streetball/Basketball

Code sample - Entity

@Entity public User extends Model {
  public String name;
  public String password;
  public void setPassword(String pwd) {
    password = Crypto.passwordHash(pwd);
  }
  public boolean hasPassword(String pwd) {
    return Crypto.passwordHash(pwd).equals(password);
  }
}

Code sample - Controller

public static void setPassword(String nw, String old) {
  User user = User.findById((Long)session.get("id"));
  notFoundIfNull(user);
  if (!user.hasPassword(old)) {
    index();
  }
  user.password = nw;
  user.save();
  render(user);
}

Property enhancement

  • What happens in user.password = nw;?
  • According to the documentation: The setter is called - but how?
  • Solution: play.classloading.enhancers.PropertiesEnhancer
  • Performing bytecode enhancement via javassist library
  • Enhancer loops over all fields, checks if getters/setters exist, creates them if necessary
  • Next step: Intercept all field accesses and redirect to getters/setters
  • Similar: Project Lombok, Scala, many other languages

Example getter/setter creation with javassist

String code =
    "public String getName() { return this.name; }";
CtMethod getMethod = CtMethod.make(code, ctClass);
ctClass.addMethod(getMethod);

code = "public void setName(String value) " + 
  "{ this.name = value; }";
CtMethod setMethod = CtMethod.make(code, ctClass);
ctClass.addMethod(setMethod);

Intercept field access, replace with getter/setter

  • ExprEditor from javassist is used, can be used to replace calls inside a method body, instead of replacing the method itself
  • Intercepts field access, no matter if in model, controller or template
  • Invokes either FieldAccessor.invokeReadProperty() or FieldAccessor.invokeWriteProperty(), which calls either getter or setter

Finders in models

  • How does User.find("byName", "alexander"); work?
  • Core problem: Model.find() is a static method
  • Nothing is overwritten in the User entity
  • Hierarchy: User > Model > GenericModel > JPABase
  • GenericModel:
    public static JPAQuery find(String query, Object... params) {
      throw new UnsupportedOperationException("Please 
        annotate your JPA model...");
    }

play.db.jpa.JPAEnhancer

Enhancer loops through all entities and adds static methods
// public static play.db.jpa.GenericModel.JPAQuery
// find(String query, Object[] params) {
//   return play.db.jpa.JPQL.instance.find(entityName, 
//     query, params);
// };
CtMethod find = CtMethod.make(code, ctClass);
ctClass.addMethod(find);

play.db.jpa.JPAEnhancer

  • Similar code applies to other static methods
  • count(), count(query, params)
  • findAll(), findById(id), find(query, params), findOneBy(query, params)
  • deleteAll(), delete(query, params)
  • create()

Deep dive: Disassembling bytecode

  • Problem: Debugging is hard and sucks
  • Back to the compile, restart cycle
  • You never have exact info how bytecode was applied
  • Solution: javap -verbose -c User
  • Better solution: JD-Gui

Controller redirects

Calling another controller in java issues a HTTP redirect
public static void redirect() {
  index();
}
 curl -v localhost:9000/redirect
< HTTP/1.1 302 Found
< Location: http://localhost:9000/

Controller redirects - logic

  • play.classloading.enhancers.ControllersEnhancer
  • Every action is enhanced
  • As soon as an action is started, a boolean is set
  • On every action invocation this boolean is checked
  • If it is already true, this means the action is called from another java method and a redirect is issued
  • If it is false, the controller logic is executed

Controller redirects - bytecode

Inserted before every controller call
if(!play.classloading.enhancers.ControllersEnhancer.
    ControllerInstrumentation.isActionCallAllowed()) {
      play.mvc.Controller.redirect(
        "controllers.Application.index", $args);
      return;
    }
    play.classloading.enhancers.ControllersEnhancer.
      ControllerInstrumentation.stopActionCall();
  

Disassembled controller code - is different

if (!ControllersEnhancer.
  ControllerInstrumentation.isActionCallAllowed()) {
  Controller.redirect("controllers.Application.index",
    new Object[0]);
} else {
  ControllersEnhancer.ControllerInstrumentation
    .stopActionCall();
  // controller logic comes here...
}

Rendering context

How does this work?
List<User> users = User.findAll();
String message = "FooBarBaz";
render(users, message);
<div>${message}</div>
#{list users, as:'user'}
<li>${user.name}</li>
#{/list}

Rendering context - how it works

play.classloading.enhancers.LocalvariablesNamesEnhancer
  • Every variable declaration in an action creates a sort of history entry in LocalVariablesNamesTracer.localVariables
  • When rendering the template every object added to the render() method is checked with this localVariables variable
  • Example:
    List<User> users = User.findAll();
    LocalVariablesNamesTracer.addVariable("users", users);

Rendering context - how it works

protected static void renderTemplate(String templateName, 
 Object... args) {
  Map templateBinding = new HashMap(16);
  for (Object o : args) {
    List names =
      LocalVariablesNamesTracer.getAllLocalVariableNames(o);
    for (String name : names) {
      templateBinding.put(name, o);
    }
  }
  renderTemplate(templateName, templateBinding);
}

Questions?

Books: Introducing the Play! framework

Introducing the Play! framework
by Wayne Ellis

Books: Play Framework Cookbook

Play Framework Cookbook
by Alexander Reelsen

Resources