読者です 読者をやめる 読者になる 読者になる

( ꒪⌓꒪) ゆるよろ日記

( ゚∀゚)o彡°オパーイ!オパーイ! ( ;゚皿゚)ノシΣ フィンギィィーーッ!!!

Java Webフレームワーク Play!で遊んでみる - Sample Application

Page not found — Playframework
ってことで、公式にあるサンプルアプリケーションを作ってみます。

サンプルアプリケーションは、写真のアップロードアプリ(Photosアプリケーション)です。
playではファイルアップロードが簡単に扱えるので、作るのはすぐでした。

オブジェクトモデル

今回のPhotosアプリケーションでのモデルは、Photo.ClassというPOJOです。
こいつはDatabaseとのマッピングは行わないで、アプリケーションディレクトリ以下のdataディレクトリに
置かれた画像ファイルを表すモデルです。

ってことで、app/models/Photo.javaを作成します。

package models;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import play.Play;
import play.libs.Files;

public class Photo {
    
    public File file;
    
    public Photo(File file) {
        this.file = file;
    }

    public String getName() {
        return file.getName();
    }
    
    public Long getSize() {
        return file.length();
    }

    public static List<Photo> findAll() {
        List<Photo> all = new ArrayList<Photo>();
        for (File file : Play.getFile("data").listFiles()) {
            all.add(new Photo(file));
        }
        return all;
    }
    
    public static Photo findByName(String name) {
        return new Photo(Play.getFile("data/" + name));
    }
    
    public static Photo create(File file) {
        File to = Play.getFile("data/" + file.getName());
        Files.copy(file, to);
        return new Photo(to);
    }
}

play.PlayクラスのgetFile("data")で、アプリケーションディレクトリ以下のdataディレクトリのFileオブジェクトを
取得しています。

コントローラ

まずは、indexページ(http;//localhost:9000/)にアクセスされた時に、dataディレクトリ内の画像を一覧表示するようにします。

indexページにアクセスされた際には、あらかじめ生成されているapp/models/controllers/Application.javaのindexメソッドが呼び出されるので、このメソッド内で、先ほど用意したPhotoクラスのfindAll()メソッドを呼び出してテンプレートに渡すように修正します。

package controllers;

import java.util.List;
import models.Photo;
import play.mvc.Controller;

public class Application extends Controller {

    public static void index() {
        List<Photo> photos = Photo.findAll();
        render(photos);
    }
    
}

render()メソッドは、テンプレートの出力を行うメソッドです。テンプレートファイルは、コントローラのクラス名とAcitonメソッド名から自動的に判定されます。

app/views/<コントローラ名>/.html


今回はデフォルトで用意されているApplicationコントローラなので、app/views/Application/.htmlとなります。

indexページのテンプレート

で、出力に使用するテンプレートファイルですが、app/views/Application/index.htmlとなります。
このファイルはあらかじめ用意されているので、以下の内容に変更します。

#{extends 'main.html' /}
#{set title:'All photos' /}

<h1>${photos.size() ?: 'No'} photo(s) found</h1>

#{list items:photos, as:'photo'}
<div class="photo">
    <img src="@{Application.photoContent(photo.name)}" height="100" />
    <div>
        <span class="name">${photo.name}</span>
        <span class="size">${photo.size}</span>
    </div>
</div>
#{/list}

ちょっと不思議なことに、テンプレート内からはActionメソッド内でrender()メソッドに引き渡したローカル変数名で
PhotoオブジェクトのListにアクセスしています。

で${photos.size() ?:No}でListが0件だったらNoという文字列を出すようになってます。

#{list items:photos,as 'photo'} から #{/list}まではコレクションの繰り返しです。


<img>タグのsrcに指定されている"@{Application.photoContent(photo.name)}"は、ApplicationコントローラのphotoCOntentメソッドへのURLを生成する処理です。

メソッド引数には、Photoオブジェクトの名前を渡しています。


photoContentメソッドでは、photoオブジェクトの名前(=ファイル名)を元に画像ファイルをdataディレクトリから取得し、バイナリとしてレスポンスを返すように実装する必要があります。


したがって、Applicationコントローラに、以下のようにphotoContentメソッドを追加します。

public static void photoContent(String name) {
    Photo photo = Photo.findByName(name);
    renderBinary(photo.file);
}

URLとのマッピング(routing)定義

Applicationコントローラに二つのActionメソッドを定義したので、これらに対してURLのマッピング(routing)を定義します。

このマッピング(routing)は、cont/routesというファイルに定義されています。

GET    /                   Application.index
GET    /{name}/content     Application.photoContent

ここまでで、http://localhost:9000/にアクセスされたときにApplication.indexメソッドを呼び出すのと、
http://localhost:9000/<画像ファイル名>/contentにアクセスされたときにphotoContentメソッドを呼び出すように定義されました。

あとは、dataディレクトリに適当な画像ファイルをいくつか配置して、http://localhost:9000/にアクセスすると画像の一覧が表示されます。

ファイルアップロード処理

画像ファイルをアップロードする処理を追加します。
以下の2つの処理を実装していきます。

  1. アップロードするフォームを表示する処理
  2. アップロードされたファイルをdataディレクトリに保存する処理

Applicationコントローラに以下のメソッドを追加します。

public static void newPhoto() {
    render();
}
 
public static void upload(File photo) {
    Photo.create(photo);
    index();
}

newPhotoメソッドはapp/views/Application/newPhoto.html を出力するだけです。
なので、app/views/Application/newPhoto.html を新規に作成します。

#{extends 'main.html' /}
#{set title:'Add a photo' /}
 
<h1>New photo</h1>
 
<form action="@{Application.upload}" method="POST" enctype="multipart/form-data">
    <label for="photo">Choose a file to send : </label> 
    <input type="file" id="photo" name="photo" />
    <p>
        <input type="submit" value="Send it ..." />
    </p>
</form>

formのactionには、ApplicationコントローラのuploadメソッドへのURLを生成したものが指定されています。

formの内部では<input type="file">のnameに"photo"と指定されており、これがApplicationコントローラのuploadメソッドの引数( File photo)に対応しています。


このフォームから送信されたファイルはFileオブジェクトとしてuploadメソッドに渡されるので、後はPhotoクラスのcreateメソッドを呼び出してファイルをdataディレクトリに保存しています。


アップロードするフォームができたので、リンクをindexページに追加します。
app/views/Application/index.htmlの最後に、以下のようなリンクを追加します。

<a class="new" href="@{Application.newPhoto}">New photo</a>

最後に、URLについてのroutingをconf/routesファイルに追加します。
uploadのみ、POSTで指定されています。

GET     /new          Application.newPhoto
POST    /             Application.upload

CSSの指定

アプリケーションに適用されるCSSファイルは、public/stylesheets/default.css に配置されているので、内容を以下のように変更します。

.photo {
    border: 1px solid #aaa;
    padding: 4px;
    float: left;
    height: 150px;
    width: 150px;
    text-align: center;
    margin: 0 10px 10px 0;
}
 
.photo img {
    margin-bottom: 5px;
}
 
.photo .name {
    display: block;
    white-space: nowrap;
    width: 142px;
    overflow: hidden;
}
 
.photo .size {
    font-weight: bold;
    white-space: nowrap;
    display: block;
}
 
.new {
    clear: both;
    margin-top: 1em;
    display: block;
}

これでPhotosアプリケーションが完成しました。

動かしてみての感想

playコマンドによるアプリケーションの生成や、ディレクトリ構成、routing情報の設定など、
随所にRuby on Railsの影響が見られるフレームワークだと感じました。


おもしろいのは、メソッド引数にパラメータをバインドするアーキテクチャと、Groovyによるテンプレートかな。
ファイルをいろいろと扱うアプリケーションを作るのにはすごく向いてるかも。