Friday, July 8, 2011

Play Framework Tutorial



A couple of weeks ago, I attended a introductory one-hour session about Play Framework. And it was interesting as it was my first time to experience anything related to the web. So I decided to give it a try. And guess what, I could navigate inside the framework and make my first simple service within two hours (remember: zero web experience and not so much Java experience), And thought I could write about it. Why? Because I wanted a tutorial that gives me the basics without going deeps, then gives me the keywords and options to go deeper in any part, and did not find what I want, so I did it my way.

Note: Even for the front-end developer, knowing the basics of web applications. Why? Imagine making an application that depends on a web service, say, BBC RRS feed). To test the application you have to make some scenarios like: service down, content with special characters, etc. So you have to create a "development environment" different from the "production environment" (which usually cannot be halted for some front-end testing).

In this tutorial I will make a service that takes a city name and returns a four-day forecast from Google weather API.

Installation:

This is straight forward. Just go to http://www.playframework.org/ and download the latest version. (for Ubunutu, I had to go to http://download.playframework.org/miscellaneous/ to download the debian package)

Starting a new Project:

[I'm using Play 1.0.2.1 on Ubuntu 10.10]
1- Open Terminal
2- Assuming we want to make a project called "MyWeatherService", write:
play new MyWeatherService

3- It will ask for application name (the previous name was the project name, so write:
MyWeatherService

4- Now the project is created and can be accessed by typing:
play run MyWeatherService



5- Now the service is running. Open your web browser and type
http://localhost:9000/
(9000 is the default port, let's see this later)

6- To stop the service, just go back to the terminal and press: Ctrl+C


Editing project from NetBeans:

For easier development, the Play! project can be opened in different IDE's like NetBeans and Eclipse. So I'm going to use NetBeans.

1- From terminal type:
play netbeansify MyWeatherService
(there is also "eclipsify")

2- From NetBeans: File -> Open Project

3- In my case, the project is located in
/home/adly/MyWeatherService


Project's Basic Structure:

Let's take an overview on the must-know basics before proceeding, The Play! project follows the MVC stucture, so We are dealing with:
Model: where database queries, mathematical computations .. etc happen
View: the template used for rendering the data from the model. The view can be HTML/XML.
Controller: maps model data to fit in the view.

and there is and important file: conf -> routes. This file defines how the user can talk to the web service, and which controller handles each route.


Creating our web service:

1- Lets create the model first. Right click on app -> models and choose new -> Java Class .And let's name it WeatherModel.



2- Make this class extend Model.

3- Create a method to be interfaced by the controller. I made a getAllWeatherStates() method which returns an array list of the weather states representing today and some days to come. I'm not going to use databases ad relations and all these stuff, I want to keep it simple as a beginning. So all I'm going to do is call a Google weather API, parse data, create WeatherState objects, put them in an ArrayList and return this ArrayList to the caller.

First, I created a simple class and called it WeatherState that has nothing but strings of data/state/max/min.

[WeatherState.java]
package models;

public class WeatherState {
    
    public String date;
    public String state;
    public String max;
    public String min;
    
    public WeatherState(String date, String state, String max, String min){
        this.date = date;
        this.state = state;
        this.max = max;
        this.min = min;
    }
}

Now let's see the code of the our model

[WeatherModel.java]
/**
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package models;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import play.db.jpa.Model;

/**
 *
 * @author adly
 */
public class WeatherModel extends Model{
    
    /**
     * A controller should call this method
     */
    public static ArrayList getAllWeatherStates(String cityName){
        try {
            return getCityStates(cityName);
        } catch (IOException ex) {
            ArrayList weatherStates = new ArrayList();
            return weatherStates;
        }
    } 
    
    
    /**
     * A private method that calls a Google weather API, parses content,
     * and returns an ArrayList on WeatherState objects
     */
    private static ArrayList getCityStates(String cityName) throws IOException {
        
        ArrayList weatherStates = new ArrayList();
        try{
            
            /**
             * Download XML data
             */
            
            URL connectURL = new URL("http://www.google.com/ig/api?weather="+cityName);
            HttpURLConnection conn = (HttpURLConnection)connectURL.openConnection(); 
            
            conn.setDoInput(true);
            conn.setDoOutput(true);
            conn.setUseCaches(false); 
            conn.setRequestMethod("GET"); 
            // connect and flush the request out
            conn.connect();

            InputStream inStream = conn.getInputStream();
            
            /**
             * Parse data into an ArrayList
             */
            
            try{
                DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
                DocumentBuilder builder = factory.newDocumentBuilder();
                Document dom = builder.parse(inStream);
                Element root = dom.getDocumentElement();
                NodeList items = root.getElementsByTagName("forecast_conditions");
  
                for(int i=0; i<items.getLength(); i++){
                    String day = "";
                    String low = "";
                    String high = "";
                    String condition = "";
                    
                    Node item = items.item(i);
                    NodeList properties = item.getChildNodes();
                    for(int j=0; j<properties.getLength(); j++){
                        Node property = properties.item(j);
                        String name = property.getNodeName();
                        if(name.equalsIgnoreCase("day_of_week")){
                            Element child = (Element) property;
                            day = child.getAttribute("data");
                        }else if(name.equalsIgnoreCase("low")){
                            Element child = (Element) property;
                            low = child.getAttribute("data");
                        }else if(name.equalsIgnoreCase("high")){
                            Element child = (Element) property;
                            high = child.getAttribute("data");
                        }else if(name.equalsIgnoreCase("condition")){
                            Element child = (Element) property;
                            condition = child.getAttribute("data");
                        }
                    }
                    WeatherState weather = new WeatherState(day, condition, high, low);
                    weatherStates.add(weather);
                }
            }
            catch(Exception e)
            {
                //parsing exception
            }
        }
        catch(Exception e)
        {
            //downloading exception
        }

        return weatherStates;
    } //end of 'getCityStates' method
    
}

Note that the code of getCityStates method is pure Java, I actually used it before in an android project.

So this is all we had to make for or model. And the big part of "coding" is done.


4- Now let's make the controller. It is the one containing a method the will be called when a user requests with a certain URL (to see that later).

5- Right click on app -> controllers and choose new -> Java class. And let's name it Weather.



6- Make it extend Controller. And let's make a requestWeather method that will call the model and then the appropriate view by a method called render().

7- The code is as simple as this:

[Weather.java]
/**
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package controllers;

import java.util.ArrayList;
import models.WeatherModel;
import play.mvc.Controller;

/**
 *
 * @author adly
 */
public class Weather extends Controller{
    
    /**
     * Called when a user requests weather with a certail URL containing
     * city name
     */
    public static void requestWeather(String cityName) {
        ArrayList weatherStates = WeatherModel.getAllWeatherStates(cityName);
        render(weatherStates);
    }
}


8- Now let's make the view for this controller. I want to make an XML file that appears to the user when the weather of a certain city is requested. And views an error message if city name is not valid.

9- Right click on app/views -> new -> other and chose a new folder with name: Weather (the same name of the controller)





10- Right click on Weather folder and choose new -> XML Document and name it requestWeather (the same name of the controller requesWeather() method)





11- The content of returned XML file will be multiple blocks of "forecast" tags, so I made a loop on the weatherStated list passed to this XML view by the render() method in the controller. An if-else condition is added to view an error message is case of an invalid city name.

[requestWeather.xml]
#{set title:'Weather Forecast' /}

#{ifnot weatherStates}
<forecasts>
<error>city not available</error>    
</forecasts>
#{/ifnot}

#{else}
<forecasts>
    #{list items:weatherStates, as:'weather'}
    <forecast>
        <date>${weather.date}</date>
        <state>${weather.state}</state>
        <max>${weather.max}</max>
        <min>${weather.min}</min>
     </forecast>
    #{/list}
</forecasts>
#{/else}

12- Now we are almost done. The only missing thing is defining a URL the user can use to access this service. So let's go to the conf -> routes file and add the following line

[routes]
# Request Weather Forecast
GET     /forecast/{cityName}                    Weather.requestWeather(format:'xml')
This line defines the format concatenated to the service IP & port to access the weather service. I chose to add the "forecast" word first, then accept the "cityName" variable from user. When a URL with this format is called, The requestWeather method in the Weather class (the controller) will be called and will render the output as xml.

13- Now the service is ready to run. Just save your work and make sure the service is running (from terminal window) and call the following two URLs to see the results:
http://localhost:9000/forecast/cairo
<forecasts>
     <forecast>
        <date>Fri</date>
        <state>Scattered Thunderstorms</state>
        <max>91</max>
        <min>74</min>
     </forecast>
     <forecast>
        <date>Sat</date>
        <state>Scattered Thunderstorms</state>
        <max>93</max>
        <min>74</min>
     </forecast>
     <forecast>
        <date>Sun</date>
        <state>Scattered Thunderstorms</state>
        <max>93</max>
        <min>74</min>
     </forecast>
     <forecast>
        <date>Mon</date>
        <state>Scattered Thunderstorms</state>
        <max>94</max>
        <min>75</min>
     </forecast>
</forecasts>


http://localhost:9000/forecast/cair
<forecasts>
    <error>city not available</error>
</forecasts>

Note 1: A good thing about Play! Framework is that you do not need to compile anything after editing your code. Just save the edited file and refresh the requested page.

Note 2: Although your service is running fine, it is still not ready for production for many reasons. For example, try the following URL:
http://localhost:9000/@kill
This kills the service. And it happens because you are running in something called "dev mode" not "prod mode". Please check conf -> application.conf file for more details and options

[application.conf]
# Application mode
# ~~~~~
# Set to dev to enable instant reloading and other development help.
# Otherwise set to prod.
application.mode=dev


Done! This was a quick tutorial to get hands into the framework. You can enjoy the documentation on their website, it is a very useful one.

Download this example from here.


Enjoy :)

1 comment:

  1. Hi Mahmoud,

    This is regarding a review request for the book: Play Framework Cookbook, recently released by Packt.

    I came across your blog through google, and have found it informative in terms of the content your blog provides.

    I believe you'd be an ideal person to review the book, and if you are interested in reviewing it then I'd be glad to send you an e-copy of it.

    For complete information about the book, please visit: http://www.packtpub.com/play-framework-cookbook/book

    You can contact me on jiteshg@packtpub.com for more details. Thank you :)

    ReplyDelete