Monday, May 23, 2016

Migration from Play 2.4 to Play 2.5

The Play 2.5 migration guide gives general guidelines of what to do. As usual in Play documentation only few issues are really explained in details with "how to do".
This post fills the gaps (in scope of my experience in migrating the project).

Installation


The procedure in this post refers to existing Play 2.4 installation, which was done according to the instructions in the post Getting started with Play Framework.

1. Download activator zip for Play 2.5 from www.playframework.com/download and extract files to the target Play folder, for example to:
C:\Play-2.5.3\activator-dist-1.3.10
2. Update the environment variable PLAY_HOME according to Play 2.5 installation path. Pay attention, that it should have bin in the end, for example:
C:\Play-2.5.3\activator-dist-1.3.10\bin;
The installation of Play 2.5.3 (the last 2.5 stable release) comes with the minor problem. To fix it:
1. Edit the file activator-dist-1.3.10\bin\activator.bat and add the "%" character at the end of line 55. The proper line should be
set SBT_HOME=%BIN_DIRECTORY%
2. Create subdirectory conf under the activator root directory activator-dist-1.3.10.
3. Create in the conf directory an empty file named sbtconfig.txt.

Build changes, scala and sbt versions


1. Change scala version in build.sbt:
scalaVersion := "2.11.7"
2. Change the Play version number in projects/plugins.sbt:
addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.5.3")
3. Change plugins for later versions in projects/plugins.sbt:
addSbtPlugin("com.typesafe.sbt" % "sbt-less" % "1.1.0")
addSbtPlugin("com.typesafe.sbt" % "sbt-play-ebean" % "3.0.0")

sbt version

I did not follow the recommendation of the migration guide to upgrade the sbt version from 0.13.8 to 0.13.11.
When I tried to do this, I get the sbt compilation errors.

If you are ready to move to sbt 0.13.11:
1. Download sbt installation from www.scala-sbt.org.
2. Uninstall the existing sbt (via Control Panel\All Control Panel Items\Programs and Features).
3. Install new sbt.
4. Change in project/build.properties the sbt version:
sbt.version=0.13.11
5. Compile the project and fix the sbt compilation errors (I gave up, so I can't help here).

API changes


Play 2.5 is better adapted to Java 8 and Play F types were substituted with Java 8 types. The Java Migration Guide gives the detailed mapping of removed types to relevant Java types.
The instructions contain all necessary details for replacement and there is no point to repeat them here.

You cannot go forward due to compilation errors till all the types are substituted.

Security Filter


There is the change in the EssentialFilter API.

My application defines custom filters for security. Filter initialization was changed.
An old implementation:
public EssentialFilter[] filters() {  
     return new EssentialFilter[] {securityHeadersFilter};  
}  

A new implementation:
public EssentialFilter[] filters() {  
     return new EssentialFilter[] {securityHeadersFilter.asJava()};  
}  

Full code of the custom application filter :
 package filters;  
 import javax.inject.Inject;  
 import play.filters.headers.SecurityHeadersFilter;  
 import play.http.HttpFilters;  
 import play.mvc.EssentialFilter;  
 public class ApplicationFilters implements HttpFilters {  
      @Inject  
      SecurityHeadersFilter securityHeadersFilter;
 
      @Override  
      public EssentialFilter[] filters() {  
           return new EssentialFilter[] {securityHeadersFilter.asJava()};  
      }  
 }  

Access to application and application configuration


Implementation differs for a controller and not a controller. This is due to Play, which respects injection annotations inside a controller, but ignores them in other classes.

Access to application configuration from a controller

In Play 2.4 application could be accessed from a controller like this:
play.Play.application()
In Play 2.5 it is:
Play.current()
In Play 2.4 application configuration could be accessed from a controller like this:
play.Play.application().configuration().getString("SomePropertyName");
In Play 2.5 it is quite different:
1. A configuration should be defined in the controller as an injected data member
@Inject 
private Configuration configuration;
2. The configuration data member is accessed from the controller code
configuration.getString("SomePropertyName");

Example

The example shows a controller, which checks existence of a file on disk.
It reads the full file name from the application configuration.
 package controllers;  
 import javax.inject.Inject;  
 import play.Configuration;  
 import play.api.Play;  
 import play.mvc.Result;  
 public class Application extends Controller {  
      private static final String HEART_BEAT_PATH_PROPERTY = "heartBeatPath";  
      private static final String HEART_BEAT_CONTENT_PROPERTY = "heartBeatContent";  
      private static final String HEART_BEAT_NOTFOUND_MSG = "HeartBeat was not found";
      @Inject   
      private Configuration configuration;
      /**  
      * Looks for existence of heartBeat file, but it does not render it :   
      * returns 200 in case of the file was found and 404 if file was not found.  
      *   
      * @return  
      */  
      public Result getHeartBeat(String path) {  
            final String heartBeatPath = configuration.getString(HEART_BEAT_PATH_PROPERTY);  
            final String heartBeatContent = configuration.getString(HEART_BEAT_CONTENT_PROPERTY);  
            if (Play.current().getFile(heartBeatPath).exists()) {  
                return ok(heartBeatContent);  
            }  
            else {  
                return notFound(HEART_BEAT_NOTFOUND_MSG);  
            }  
      }  
}  

Access to application configuration from other class (not a Controller)

In Play 2.4 application configuration could be accessed like this:
play.Play.application().configuration().getInt(SESSION_TIMEOUT, SESSION_TIMEOUT_DEFAULT);
In Play 2.5:
1. A configuration should be defined as an injected data member. Since @Inject annotation does not work in not-controller class, injection should be done by explicit call to injector:
private Configuration configuration = Play.current().injector().instanceOf(Configuration.class);
2. The configuration data member is accessed from code
configuration.getInt(SESSION_TIMEOUT, SESSION_TIMEOUT_DEFAULT);

Access to JPA


Global static API JPA was removed from Play 2.5 and the JPAApi interface should be used instead.
The migration guide states, that the JPAApi should be injected, but does not elaborate this.

Using JPA in a controller

If JPA is used in a controller, migrating should be done similar to the configuration example.
1. Define the injected data member
@Inject 
private JPAApi jpa;
2. Replace in the controller code JPA (static API) with jpa (data member)
// Play 2.4 
 JPA.em()...
 // Play 2.5
 jpa.em()...

The method does not work, if JPA is used in class, which is not a controller or a controller member.

Using JPA in other class (not a controller)

I prefer implement JPA-related stuff out of a controller.
Namely, there is an utility class DbProcExecutor, which encapsulates JPA calls and exposes the static API for DB operations:
public static <T> DbOperationResult<T> executeDbProc0(Mode mode, DbProc0<DbOperationResult<T>> dbProc ) {  
    ...  
}
  
public static <I,T> DbOperationResult<T> executeDbProc1(Mode mode, I input, DbProc1<I, DbOperationResult<T>> dbProc) {  
    ...  
}  

The following method was used for DbProcExecutor migration:
1. A new static member is initialized by injection
private  static final JPAApi jpa = Play.current().injector().instanceOf(JPAApi.class);
2. Calls to JPA are replaced by jpa.

DbProcExecutor after substitution:
 public final class DbProcExecutor {  
      public static enum Mode{  
           read,  
           write;  
      }  
      private static final JPAApi jpa = Play.current().injector().instanceOf(JPAApi.class);

      /**  
       * Invokes the DB procedure of type DbProc0 in a scope of JPA transaction.  
       *   
       * @param mode - read or write mode  
       * @param dbProc  
       * @return  
       */  
      public static <T> DbOperationResult<T> executeDbProc0(Mode mode, DbProc0<DbOperationResult<T>> dbProc ) {  
           try {  
                boolean readOnly = (mode==Mode.read);  
                return jpa.withTransaction("default", readOnly, dbProc);  
           }  
           catch (QueryTimeoutException e) {  
                return handleTimeOutException(e);  
           }  
           catch(Throwable e) {  
                return handleException(e);  
           }   
      }
 
      /**  
       * Invokes the DB procedure of type DbProc1 in a scope of JPA transaction.  
       *   
       * @param mode - read or write mode  
       * @param input  
       * @param dbProc  
       * @return  
       */  
      public static <I,T> DbOperationResult<T> executeDbProc1(Mode mode, I input, DbProc1<I, DbOperationResult<T>> dbProc) {  
           try {  
                dbProc.setInput(input);  
                boolean readOnly = (mode==Mode.read);  
                return jpa.withTransaction("default", readOnly, dbProc);  
           }  
           catch (QueryTimeoutException e) {  
                return handleTimeOutException(e);  
           }  
           catch(Throwable e) {  
                return handleException(e);  
           }  
      }
      ...  
 }  

Removing GlobalSettings


The migration guide recommends to eliminate the GlobalSettings implementation class.
Differently from the steps, explained earlier, this step is optional.
If the only change left is the GlobalSettings migration, your application should be compiled without errors and run smoothly.
That is the reason, why I do not explain about GlobalSettings migration here and do it in a separate post.

No comments :

About the author

My Photo
I trust only simple code and believe that code should be handsome. This is not a matter of technology, but professional approach, consolidated after years of software development. I enjoy to cause things working and feel very happy, when I manage to solve a problem.
Back to Top