Play is not "just another framework". It is remarkably powerful given how simple it is to use, but under the covers, it does some unusual things which require a bit of special handling.
The issuesThe main thing about Play is that it has non-standard class loading. In particular, it can load and instrument classes on the fly, possibly without writing them to disk. ABL needs to analyze the bytecode of the logic classes to calculate the dependency graph, but the bytecode is not available using normal mechanisms (this is the subject of quite a bit of discussion in the Play community).
In addition, Play has a tendency to flush objects out of the persistence context when they are saved, which affects the business logic which executes as commit time.
Finally, Play also uses its own Hibernate interceptor to determine whether an object requires saving or not.
The solutionsFortunately, all these details are taken care of with two classes that are included in the demo:
ABLPlugin takes care of pre-loading the classes' bytecode into the ABL engine for analysis. This plugin must be activated by putting it in the
play.plugins file, which must be in the classpath.
ABLEntityProcessor is registered with the ABL engine in
ABLConfig.properties. It takes care of re-attaching persistent objects with the current session, and saving them in a Play-friendly way.
These are the only two things that need to be in place to use ABL with a Play application.
Implementation notesThe following is of interest only if you want to know the gory details.
The three issues we hit were:
- Play's classloading makes it difficult to access the bytecode of application classes
- Play flushes JPA objects on save, which removes them from the persistence context
- Play implements its own Hibernate interceptor, which decides whether an object gets saved or not
1 - Classloading
Our biggest obstacle was definitely Play's unusual classloading. As many people know, Play dynamically instruments the bytecode of app classes, which means that it can be difficult to get access to that bytecode yourself at runtime (even just to analyze it). The standard mechanisms (e.g. Class.getResourceAsStream) does not work.
We do require access to the bytecode for some classes so that we can look for certain dependencies. This is only for analysis: we do not change the bytecode in any way.
After some hunting, we found that, in dev mode, the enhanced bytecode is available from:
In production mode, though, this does not work (at least on Heroku), as the bytecode instrumentation happens before the start of the application, and
enhancedByteCode is null at runtime. It looks like it may be possible to read that bytecode from the (undocumented) location in which it is saved, but that seems really brittle.
We're not the first ones to encounter this. We'd like to join our voice to those who plead for a full implementation of
ApplicationClassLoader.getResourceAsStream for enhanced classes. That would make a lot of things so much easier.
2 - Flush on savePlay's
JPABase._save() does a flush any time a persistent object is saved. While this may not seem unreasonable, it does mean that, if any further processing is required, for instance in a JPA or Hibernate listener, that object will not be automatically saved, because it has been flushed from the persistence context. Any Hibernate or JPA listener will therefore have to reattach the object to the session by doing something like (in the Hibernate case):
3 - Hibernate interceptorEven if the object is reattached to the session, though, that's still not enough, because Play has its own Hibernate interceptor, which decides whether an object needs to be saved. Therefore, if a JPA or Hibernate listener wants to make sure that an object will be saved if it's modified, it will need to call something like: