There are a handful of articles on how to build a module for version 2.x of the Play Framework. While those articles are an excellent starting point, they were lacking information on configuration, exposing a plugin, and publishing them to maven central. This post will explain how to build, maintain, publish, and use your Play Framework 2.x module in other projects.
Create your empty module project
The root project directory will be called play-module-twitter
. Inside that directory there will be two sub-directories named module
and sample
. First, create your root project directory:
mkdir play-module-twitter cd play-module-twitter
To create a Java-based module, do the following in your root project directory code>play-module-twitter :
play new module What is the application name? [module] > mfz-play-module-twitter Which template do you want to use for this new application? 1 - Create a simple Scala application 2 - Create a simple Java application > 2
Your root project directory will now contain a sub-directory called module
and once you open your module project in your IDE it will be named mfz-play-module-twitter
.
We will now do the same for the sample project that will demo the use of your module:
play new sample What is the application name? [sample] > mfz-play-module-twitter-sample Which template do you want to use for this new application? 1 - Create a simple Scala application 2 - Create a simple Java application > 2
Now will be the time to add any root project files such as your README.
touch README.md
Your project directory structure should now look like this:
play-module-twitter/ module/ sample/ README.md
Your module project/code can include almost anything a normal Play Framework project can such as controllers, views, models, and general Scala/Java code. Its primarily important to delete all configuration files so they aren't included in the final .jar -- since they will conflict with the projects that will use your module. I also like to delete any controllers, assets, tests, or views that are generally included with a standard Play project:
cd module rm -Rf conf/* public/* test/* app/controllers app/views
Modify project for IDE and eventual publishing
Since I use Eclipse as my IDE for Play projects, I now like to create the Eclipse project with the command play eclipse
.
Since modules should be published either locally or remotely so they can be used in your other Play projects, it's important to modify the project/Build.scala
file to control how your artifact is published. This is how your current Build.scala file should look like:
import sbt._ import Keys._ import play.Project._ object ApplicationBuild extends Build { val appName = "mfz-play-plugin-twitter" val appVersion = "1.0-SNAPSHOT" val appDependencies = Seq( // Add your project dependencies here, javaCore, javaJdbc, javaEbean ) val main = play.Project(appName, appVersion, appDependencies).settings( // Add your own project settings here ) }
By default Play will publish your artifact with an organization that matches your project name to your local/remote repository. If we were to play publish-local
without modifying Build.scala, the artifact would be published to {play21 dir}/repository/local/play-plugin-twitter/play-plugin-twitter_2.10/1.0-SNAPSHOT/jars/play-plugin-twitter_2.10.jar
. My organization can easily be set like so:
import sbt._ import Keys._ import play.Project._ object ApplicationBuild extends Build { val appName = "mfz-play-plugin-twitter" val appVersion = "1.0-SNAPSHOT" val appDependencies = Seq( // Add your project dependencies here, javaCore, javaJdbc, javaEbean ) val main = play.Project(appName, appVersion, appDependencies).settings( organization := "com.mfizz", organizationName := "Mfizz Inc", organizationHomepage := Some(new URL("http://mfizz.com")) ) }
The artifact will now be published to {play21 dir}/repository/local/com.mfizz/mfz-play-plugin-twitter_2.10/1.0-SNAPSHOT/jars/mfz-play-plugin-twitter_2.10.jar
. For Java developers familiar with Maven, you may wonder what the trailing "_2.10" is on your artifact -- it is added by Play's underlying use of SBT (simple build tool) to publish your library for use by a specific Scala version.
Write your module
You are now ready to start writing your module code. I put all my module code in a specific package to prevent conflicts with other modules. In the module/app
directory, I create the following package com.mfizz.play.twitter
.
Our project will include a few utility classes and one Play plugin named TwitterPlugin
. This is the special class that will be loaded at startup by Play and configured via the application.conf
config file. All the details of each file in the project won't be covered here since you can view the source at GitHub. We will just some of the more important aspects of writing a module that includes a plugin.
Add a plugin that's configured at startup
Plugins for play extend the class play.Plugin
and can override the method onStart()
which is run when Play starts/restarts. This is where the code will be added to configure TwitterPlugin
from application.conf
. Your constructor will be passed one parameter which is of type play.Application
that you will want to save in your class.
public TwitterPlugin(Application application) { this.application = application; }
The application.conf
config file will support a new namespace called twitter
that will contain the settings the plugin requires. The config file will eventually look like this:
# Twitter Plugin twitter.access-token = "replace with access token from dev.twitter.com" twitter.access-secret = "replace with access secret from dev.twitter.com" twitter.consumer-key = "replace with consumer key from dev.twitter.com" twitter.consumer-secret = "replace with consumer secret from dev.twitter.com" twitter.refresh-interval = 60m
Using the application object saved in your constructor, the onStart
method will use it to get a reference to the play.Configuration
object.
@Override public void onStart() { play.Configuration configuration = application.configuration(); this.accessToken = configuration.getString("twitter.access-token"); }
Start a periodic job to refresh tweets
The onStart
method in the plugin will also start a job that runs every configured refresh-interval
to pull tweets from Twitter. If the api call fails, the existing tweets pulled from Twitter will remain in memory. The job consists of a class that implements Runnable
and instructing Akka to run it periodically.
// create job that will be run to update RefreshJob job = new RefreshJob(this); // create job to run every X milliseconds String dispatcherName = "TwitterUpdateJob"; MessageDispatcher dispatcher = Akka.system().dispatchers().lookup(dispatcherName); Akka.system().scheduler().schedule( FiniteDuration.create(0, TimeUnit.MILLISECONDS), FiniteDuration.create(this.refreshInterval, TimeUnit.MILLISECONDS), job, dispatcher );
Add external dependencies
The module depends on two external libraries. They are twitter4j-core
and twitter-text
. For Java developers familiar with Maven, normally this means the following would be added to the pom file.
<dependencies> <dependency> <groupId>org.twitter4j</groupId> <artifactId>twitter4j-core</artifactId> <version>[3.0.3,)</version> </dependency> <dependency> <groupId>com.twitter</groupId> <artifactId>twitter-text</artifactId> <version>[1.6.1,)</version> </dependency> </dependencies>
In Play (or really SBT since that's what Play uses for builds), you'll add the dependencies to your Build.scala file. Expanding on the example of Build.scala above, the appDependencies section now looks like:
val appDependencies = Seq( "org.twitter4j" % "twitter4j-core" % "[3.0.3,)", "com.twitter" % "twitter-text" % "[1.6.1,)", javaCore, javaJdbc, javaEbean )
Note that the %
symbol between the organization and artifact name is used for Java (non-Scala) Maven-based dependencies. If you use double percentage symbols such as %%
then Play will append "_2.10" onto the artifact name. A double percentage symbol %%
will instruct Play's build system SBT to only use dependencies compiled for a specific Scala version such as 2.10.
Since we added new dependencies and we'd like our IDE to include them as references, you'll also need to re-run the command play eclipse
and refresh your project to pick up the dependency changes.
Write a sample application to demo module
To speed up the code/run development cycle, its preferable to have the sample application depend on the module project so that code changes in either project will trigger re-compilation of both projects. Your sample Build.scala
project will look like:
import sbt._ import Keys._ import play.Project._ object ApplicationBuild extends Build { val appName = "mfz-play-module-twitter-sample" val appVersion = "1.0-SNAPSHOT" val appDependencies = Seq( // Add your project dependencies here, javaCore, javaJdbc, javaEbean ) // sub-project this depends on val module = RootProject(file("../module")) val main = play.Project(appName, appVersion, appDependencies).settings( organization := "com.mfizz", organizationName := "Mfizz Inc", organizationHomepage := Some(new URL("http://mfizz.com")) ).dependsOn(module) }
The two important changes are val module = RootProject(file("../module"))
that uses a relative path and adding .dependsOn(module)
to the play.Project
. When you run your sample project with the command play run
, notice that both projects are loaded:
~play-module-twitter/sample $ play run [info] Loading project definition from /play-module-twitter/sample/project [info] Loading project definition from /play-module-twitter/module/project [info] Set current project to mfz-play-module-twitter-sample (in build file:/play-module-twitter/sample/) --- (Running the application from SBT, auto-reloading is enabled) --- [info] play - Listening for HTTP on /0:0:0:0:0:0:0:0:9000
To enable the plugin, add it to a new conf/play.plugins
file:
1000:com.mfizz.play.twitter.TwitterPlugin
Note that you'll need to modify conf/application.conf with your Twitter api values for the sample app to run.
Publish module to maven central (via sonatype)
Publishing your Play2 framework module to the central maven repository will make its use by other projects a piece of cake. If you've never published any library to Maven central previously, you'll need to follow the instructions from Sonatype about setting up an account.
Assuming you've followed those instructions and have a username, password, central sync approval, and a PGP key properly setup, enabling your Play project module isn't too complicated. A helpful start for this article was locate here.
Verify your PGP key is setup:
gpg --list-keys
In your module's project directory, add addSbtPlugin("com.typesafe.sbt" % "sbt-pgp" % "0.8")
to the project/plugins.sbt
file. It will now look like this:
// Comment to get more information during initialization logLevel := Level.Warn // The Typesafe repository resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/" // Use the Play sbt plugin for Play projects addSbtPlugin("play" % "sbt-plugin" % "2.1.1") // for PGP signing addSbtPlugin("com.typesafe.sbt" % "sbt-pgp" % "0.8")
Sonatype requires a number of properties to be set before it will approve a release. These are added to the underlying "pom.xml" maven file which Play's build system SBT creates behind the scenes. You can add them by editing the Build.scala
file. The new Build.scala
file:
import sbt._ import Keys._ import play.Project._ import com.typesafe.sbt.SbtPgp._ object ApplicationBuild extends Build { val appName = "mfz-play-module-twitter" val appVersion = "1.0" val appDependencies = Seq( "org.twitter4j" % "twitter4j-core" % "[3.0.3,)", "com.twitter" % "twitter-text" % "[1.6.1,)", javaCore, javaJdbc, javaEbean ) val main = play.Project(appName, appVersion, appDependencies).settings( organization := "com.mfizz", organizationName := "Mfizz Inc", organizationHomepage := Some(new URL("http://mfizz.com")), // required for publishing artifact to maven central via sonatype publishMavenStyle := true, publishTo <<= version { v: String => val nexus = "https://oss.sonatype.org/" if (v.trim.endsWith("SNAPSHOT")) Some("snapshots" at nexus + "content/repositories/snapshots") else Some("releases" at nexus + "service/local/staging/deploy/maven2") }, // in order to pass sonatype's requirements the following properties are required as well startYear := Some(2013), description := "Play framework 2.x module to fetch, cache, and display tweets from Twitter", licenses := Seq("Apache 2" -> url("http://www.apache.org/licenses/LICENSE-2.0.txt")), homepage := Some(url("http://mfizz.com/oss/play-module-twitter")), scmInfo := Some(ScmInfo(url("https://github.com/mfizz-inc/play-module-twitter"), "https://github.com/mfizz-inc/play-module-twitter.git")), pomExtra := ( <developers> <developer> <name>Mfizz Inc (twitter: @mfizz_inc)</name> <email>oss@mfizz.com</email> </developer> <developer> <name>Joe Lauer (twitter: @jjlauer)</name> </developer> </developers> ) ) }
When you're ready to publish your module to Sonatype, run the command "play publish-signed" which will sign the artifacts using the PGP plugin and publish them to Sonatype. You'll then need to follow the instructions on Sonatype to close and release your artifacts.
Final thoughts
Your play2 framework module can include any number of plugins or utility classes. It can even include views, controllers, etc. that you may want to reuse in your own projects. You also may simply want to publish your module locally as well with the play publish-local
command.
View the source for play-module-twitter
View the project for play-module-twitter
Updates? Need assistance?
Follow @fizzed_inc on Twitter for future updates and latest news.
If you have specific issues, questions, or problems, please contact us with your inquiry or consulting request.