Writing a Play Framework Plugin in Java

I really like using the Play Framework to write simple web applications and REST interfaces. One of the interesting things about the Play Framework is that it is meant to be both stateless and asynchronous. The stateless nature of the framework makes it easy to scale very quickly, since all you need to do is deploy multiple instances of your web application in order to serve larger loads.

Sometimes however, you need to introduce some sort of mechanism to create a global variable or object. For example, if your application needs to make an expensive database connection, sometimes it is better to create a single object (or pool of objects) that can handle your database connection, and have your controllers pick those up instead of generating new ones each time a REST endpoint or webpage is served. In this post, I’ll look at creating a Play Framework plugin, and how you can maintain a global state with that plugin.

The Problem

There are times that I have an Android emulator running and I want to issue it external events programmatically. The trick here is that I want to do it from within the emulator. More specifically, when I’m running tests for an application that receives SMS messages, it’s nice to be able to trigger an inbound SMS on demand. You can trigger SMS messages by opening a telnet session to your emulator and issuing the sms command. For example, assuming your emulator is running on port 5554, a typical session might look like this:

# telnet localhost 5554
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Android Console: type 'help' for a list of commands
OK
sms send 5550100 This is a test message!!
OK

The running emulator instantly receives it:

sms-message-android

If I’m doing a lot of automated testing directly on the Android emulator, I want the emulated Android test cases to easily trigger an incoming SMS by itself. To get this functionality, I want to spin up a Play application on my testing machine that can open telnet sessions to the running emulators. The application would accept REST calls to generate the required incoming SMS, and post them to the correct virtual device.

Sidenote: how do the tests running on the Android emulator know what emulator they are running on? Easy – the last 4 digits of the emulated device’s phone number are the telnet port that it is running on. The test case can use TelephonyManager getLine1Number() to read the telephone number, and parse the port number. It then passes the port number on to the Play application when it makes the call to the REST API.

The Solution

To do this, I built a telnet manager class that would connect to the emulator, and then allow me to input commands and capture the output. To connect this telnet manager to my Play application, I needed to keep the telnet manager object around in some sort of global variable and make it accessible, since I didn’t want to connect and disconnect from the running emulator every single time I wanted to run a command.

There are two different approaches that would work for creating this type of global object. The first is to create an Actor that would work through Akka. I didn’t investigate how this was done, but instead opted to create a Play plugin. The benefit of creating a Play plugin is that they are very easy to create. The breakdown for creating the Play plugin is as follows:

  1. Create the class that you want to manage your service.
  2. Create a class that implements play.api.Plugin interface.
  3. Add the plugin to your Play configuration.

Creating the TelnetManager

There isn’t too much to say about this step of the solution. In my case, I have a simple TelnetManager class that I am using to connect to a running emulator. With my class, I can open up a connection to a running emulator, and then issue commands to it. The idea is that the Play framework application can open a connection to an emulator, and keep that connection open between different REST calls.

Since I know it’s bad if two different processes try and use the object at the same time, I used a simple lock variable to block until the resource becomes free. This ensures that two processes don’t try and talk to the same device at the same time.

Creating the Play Framework Plugin

All you need to do is implement the play.api.Plugin interface. Minimally, it looks like this:

package plugins;
 
import models.TelnetManager;
import play.Application;
import play.api.Plugin;
 
public class AndroidSMSMessagerPlugin implements Plugin {
 
    private final TelnetManager sTelnetManager = new TelnetManager();
 
    public AndroidSMSMessagerPlugin(Application app) {
    }
 
    public void onStart() {
        sTelnetManager.start();
    }
 
    public void onStop() {
        sTelnetManager.stop();
    }
 
    public boolean enabled() {
        return true;
    }
 
    public TelnetManager getTelnetManager() {
        return sTelnetManager;
    }
}

From the above code, you can see that there are only 4 methods that you need to implement in your Plugin:

  • onStart – runs any code that you need to run when the plugin is started by the Play framework. In my case, I have a start() function in my TelnetManager class that initializes a connection pool.
  • onStop – runs any code that you need to run when the plugin is stopped. In my case, I have a stop() function that destroys any open connections to any of the emulators, and releases any handles that it held open.
  • enabled – tells the Play framework if the plugin is actually enabled.
  • getTelnetManager – this is where the magic is. This function returns a handle to the globally opened TelnetManager object. With this function, I can use this in any of my controllers to actually get a reference to the global object (more on this below).

With those methods implemented, the last step is to configure your Play application to recognize the plugin.

Adding the Plugin to your Play Configuration

The easiest way to add your new plugin is to create a file in the conf of your project called play.plugins. In it, all you need is the following line:

2000:plugins.AndroidSMSMessagerPlugin

The 2000 acts as a priority level. The lower the number, the sooner the plugin is loaded (values can range from 0 – 10000). This can help you control when your plugin is loaded by the framework.

Accessing the Plugin

With everything plugged into the framework, the last thing to do is to actually access the plugin from a controller. You can do it with the following:

TelnetManager tm = Play.application().plugin(AndroidSMSMessagerPlugin.class).getTelnetManager();

As you can see, I now have a reference to the global TelnetManager object!

Wrapping Up

In this post, I went over how to create a Play framework plugin, and why you might need one. By implementing only 4 methods, you can easily create your own plugin to the Play framework.

Booting a Raspberry Pi From a 16 MB SD Card

Raspberry Pis make great little servers. With low power consumption, they make great devices if you need cheap web services running 24/7. One aspect I dislike about them however, is that they need to boot using an SD card. Given the limited number of write cycles that most SD cards have, I hate having to run anything more than the bootloader off of one.

While there are many tutorials that talk about how you can clone the SD card to a USB drive, I wanted to do something a little different. Why waste a 4 GB SD card? I have lots of smaller SD cards kicking around. In this post, I’ll discuss how I made my Raspberry Pi boot from a 16 MB SD card and use a USB hard drive for the root filesystem.

Running the Pi from a USB Hard Drive

Most tutorials have you simply clone your SD card to a USB hard drive, and then edit the cmdline.txt file to point to the USB hard drive when booting the main OS. This solves the problem of moving the root filesystem to a more robust storage medium, but ties up a 4 GB SD card that I could be using elsewhere. I know this isn’t too big of a problem, but it does mean buying a bunch of 4 GB SD cards for all my Raspberry Pis.

On a related note, I have a number of digital cameras that I have purchased over the years, and most of them came with a 16 MB SD card. Back in the day, 16 MB was a lot of storage space, but given the megapixel counts that come with most cameras, 16 MB can usually only store a few pictures. In my case, they sit in a stack in my desk drawer, gathering dusk and sulking.

This got me thinking: I’d really love to use the 16 MB card in the Raspberry Pi (plus a hard drive), and use the 4 GB card in my cameras.

Update January 20, 2015: Mike Redrobe discussed on his blog how to do this in May 2014, with great instructions on how to do this under Windows. Also, this blog noted how to do it in 2012, but without the step-by-step breakdown. Many other sources (like this blog, and this blog) outline how to modify the boot partition to mount the root filesystem from a USB drive.

What is Where

I took one of my 4 GB SD cards that had Raspbian on it, and examined how the card was formatted. There is a small MS-DOS partition used for the bootloader, and the rest of the card is used for the root filesystem. The MS-DOS partition is what interests me, because this is what the Raspberry Pi needs to see in order to bootstrap the OS. Here’s the contents:

# ls -lh /boot
total 9.7M
-rwxr-xr-x 1 root root  18K Dec 21 10:53 bootcode.bin
-rwxr-xr-x 1 root root  120 Dec 21 11:14 cmdline.txt
-rwxr-xr-x 1 root root 1.3K Dec 21 11:14 config.txt
-rwxr-xr-x 1 root root 2.3K Dec 21 10:53 fixup_cd.dat
-rwxr-xr-x 1 root root 6.0K Dec 21 10:53 fixup.dat
-rwxr-xr-x 1 root root 9.0K Dec 21 10:53 fixup_x.dat
-rwxr-xr-x 1 root root  137 Dec 24 12:29 issue.txt
-rwxr-xr-x 1 root root 3.2M Dec 21 10:53 kernel.img
-rwxr-xr-x 1 root root  19K Sep 25  2013 LICENSE.oracle
-rwxr-xr-x 1 root root 538K Dec 21 10:53 start_cd.elf
-rwxr-xr-x 1 root root 2.6M Dec 21 10:53 start.elf
-rwxr-xr-x 1 root root 3.5M Dec 21 10:53 start_x.elf

Most of those files look pretty small… hmm… I wonder how much disk space is actually used:

# df -h .
Filesystem      Size  Used Avail Use% Mounted on
/dev/sdc1        56M  9.7M   47M  18% /mnt/raspbianboot

So on the 60 MB MS-DOS boot partition, 9.7 MB worth of files are used for the bootloader. This is perfect! I can fit those files on a 16 MB SD card!

Preparing the 16 MB SD Card

Step 1 was to format the 16 MB SD card as needed in order to boot Raspbian. So, I stuck the card in the computer and took a look at the partitions on it (under Ubuntu, the device registered as /dev/sdc).

Disclaimer: I use fdisk to delete partitions on the card. If you accidentally specify the wrong device, you can destroy your hard disk partition and make your computer unbootable. These instructions are for educational purposes only. Running any of the commands below is at your own risk!

# sudo fdisk -l /dev/sdc
 
Disk /dev/sdc: 16 MB, 16056320 bytes
2 heads, 32 sectors/track, 490 cylinders, total 31360 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x00000000
 
   Device Boot      Start         End      Blocks   Id  System
/dev/sdc1              57       31359       15651+   1  FAT12

Okay, so this is the correct device – 16 MB total storage. It’s currently formatted as FAT12. For the Raspberry Pi to boot, we need a FAT32 filesystem instead. So, I started up fdisk and deleted the primary partition:

# sudo fdisk /dev/sdc
 
Command (m for help): d
Selected partition 1

Next, I created a new primary partition on the card:

Command (m for help): n
Partition type:
   p   primary (0 primary, 0 extended, 4 free)
   e   extended
Select (default p): p
Partition number (1-4, default 1): 1
First sector (2048-31359, default 2048): 
Using default value 2048
Last sector, +sectors or +size{K,M,G} (2048-31359, default 31359): 
Using default value 31359

Next, I changed the partition to W95 FAT32 (LBA). This is type ID c:

Command (m for help): t
Selected partition 1
Hex code (type L to list codes): c

Finally, I needed to mark the new primary partition as bootable:

Command (m for help): a
Partition number (1-4): 1

With that done, I saved the partition information, and quit fdisk:

Command (m for help): w
The partition table has been altered!
 
Calling ioctl() to re-read partition table.
Syncing disks.

Note: if you receive messages about the system retaining the old partition information after you create the new primary, simply remove the card from the card reader, and then re-insert it.

Finally, I created the filesystem on the new partition:

# sudo mkfs -t vfat /dev/sdc1
mkfs.fat 3.0.26 (2014-03-07)

Copying the Boot Files

The next step was to copy the boot files from the original Raspbian 4 GB card to the 16 MB card. I removed the 16 MB card from my card reader, and popped in the Raspbian 4 GB SD card. I created a mount point for it, and mounted the boot partition. Note that the first partition on the card (again registered as /dev/sdc) contains the boot partition. So /dev/sdc1 is the boot partition, while /dev/sdc2 contains the root filesystem:

# sudo mkdir /mnt/raspbianboot
# sudo mount /dev/sdc1 /mnt/raspbianboot

I needed a temporary location to store the files, so I copied them to /tmp:

# sudo cp -r /mnt/raspbianboot /tmp

So far so good. Now to put them onto the 16 MB card. I unmounted the boot partition:

# sudo umount /mnt/raspbianboot

I popped out the Raspbian 4 GB SD card and put in the 16 MB card. To keep things clear, I created a new mount point for it… just so I didn’t confuse myself with what I was mounting. Again, when I took out the 4 GB card and put in the 16 MB card, it registered as /dev/sdc:

# sudo mkdir /mnt/raspbianboot16
# sudo mount /dev/sdc1 /mnt/raspbianboot16

Next, I copied the files from /tmp to the 16 MB SD card:

# sudo cp -r /tmp/raspbianboot/* /mnt/raspbianboot16

The final thing was to modify the boot loader on the 16 MB card to boot from the USB hard drive instead of trying to boot on the second partition of the SD card (which would be bad, since there isn’t one!):

# sudo vi /mnt/raspbianboot16/cmdline.txt

I changed:

root=/dev/mmcblk0p2

To the following:

root=/dev/sda1

I also added two additional flags to the file at the end of the line:

bootdelay rootdelay

I saved the file and unmounted the drive:

# sudo sync
# sudo umount /mnt/raspbianboot16

Cloning Raspbian Wheezy to the USB Hard Drive

The last piece of the puzzle is to clone the other Raspbian root filesystem on the SD card to the USB hard drive. I have a 320 GB USB drive that I use for things like my Deer Detector. First up, I put the Raspbian 4 GB SD card back into my card reader, which registered as /dev/sdc. Next, I plugged in the new USB hard drive. In my machine, the new drive shows up as /dev/sdd. I copied the root partition from the Raspbian 4 GB SD card to the new disk. Note that since I want the root partition, it’s /dev/sdc2 that I want to copy from (remember /dev/sdc1 was the boot partition):

# sudo dd if=/dev/sdc2 of=/dev/sdd bs=4M

One final housekeeping point remained. The Raspbian root filesystem has an fstab that tries to mount / from the SD card. I needed to change that. So, I mounted the new USB hard drive under /mnt/raspbianroot and modified the file:

# sudo mkdir /mnt/raspbianroot
# sudo mount /dev/sdd1 /mnt/raspbianroot
# sudo vi /mnt/raspbianroot/etc/fstab

I needed to change the following line:

/dev/mmcblk0p2  /               ext4    defaults,noatime  0       1

To this:

/dev/sda1  /               ext4    defaults,noatime  0       1

With that done, I unmounted the USB drive, and plugged both the 16 MB SD card and the USB hard drive into the Raspberry Pi (to power the drive, I had to use a powered USB hub, but you get the idea). The Raspberry Pi booted perfectly, and I verified that it was running from the USB hard drive by checking that /dev/sda1 was mounted at /.

Note: the new partition on the USB hard drive will be exactly the size as the partition on the 4 GB SD card. Since I wanted to extend the partition to take up the full drive, I ran fdisk and printed out the current partition information, and made note of the starting block. Then, I deleted the partition, and recreated it using the same starting block. I made sure the partition spanned the entire drive. I wrote the table out, rebooted, and then ran resize2fs to resize the filesystem.

Wrapping Up

Making the Raspberry Pi boot from a 16 MB SD card was pretty easy. Essentially it involves cloning the boot partition to the smaller SD card and the root partition to some other USB storage medium. As long as the boot partition continues to be smaller than 16 MB, this trick will continue to work.