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/<コントローラ名>/
今回はデフォルトで用意されているApplicationコントローラなので、app/views/Application/
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つの処理を実装していきます。
- アップロードするフォームを表示する処理
- アップロードされたファイルを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によるテンプレートかな。
ファイルをいろいろと扱うアプリケーションを作るのにはすごく向いてるかも。