Tuesday, May 07, 2013

Define arbitrary tasks in the Play 2.1 Framework (Rakes like)

Recently with the help from James Roper from Typesafe, I added some arbitrary tasks to our application so that we can seed some data from command line. These tasks are basically like rake tasks in which you can run fire up the application environment and perform code written for you application.

The idea is to create SBT tasks and then, quoted from James, "get a hold of the dependencies, create a new URLClassLoader with them, load the class you want to use from that classloader using reflection, and then invoke it."

Here is the code in the SBT build file Build.scala
import java.net.URLClassLoader
object ApplicationBuild extends Build {
//Other project settings such as appVersion and appDependencies

def registerTask(name: String, taskClass: String, description: String) = {
  val sbtTask = (dependencyClasspath in Runtime) map { (deps) =>
    val depURLs = deps.map(_.data.toURI.toURL).toArray
    val classLoader = new URLClassLoader(depURLs, null)
    val task = classLoader.
                 loadClass(taskClass).
                 newInstance().
                 asInstanceOf[Runnable]
    task.run()
  }
  TaskKey[Unit](name, description) <<= sbtTask.dependsOn(compile in Compile)
}

val main = play.Project(appName, appVersion, appDependencies).settings(
  registerTask("seed-data-book","tasks.SeedBooks", "seed for book table" ),
  registerTask("seed-data-user","tasks.SeedUsers", "seed for user table" )
)

Here is our task classes (I placed it in app/tasks)
package tasks
import play.core.StaticApplication

abstract class Task extends Runnable { 
  val application = new StaticApplication(new java.io.File("."))
}
    
class SeedBooks extends Task{
  def run {
    Book.dao.save(new Book("Introduction to Scala", "Martin Odersky"))
  }
}
    
class SeedUsers extends Task{
  def run {
    User.dao.save(new User("jRoper", "password"))
  }
}
I hope this helps. Feel free to ask any questions in the comments.