※ 1.x 系の情報です。
2014/07/23 OAuth関連記事修正
Play framework について何か備忘録でも書いていこうかと思ったけど(Wicket の記事みたいに)、ドキュメントが充実しているのでその必要は無かった。わからないことがあった場合はとりあえずここを見ればほとんどの場合解決する。かなりわかりやすく書いてあってとても便利。他には、CodeZineの記事もわかりやすい。
Struts や Wicket などの他の Java 製 Web アプリケーションフレームワークと比べていろんな部分で簡単で作りやすいうえ、DBアクセスのための機能も備わっているのがありがたい(しかも素の JPA を使うよりもずっと簡単)。eclipse にインポートするための設定ファイルを作ってくれたり、ホットデプロイ(と呼んでいいのかそもそも疑問だが)によりサーバを起動しっぱなしでソースの修正や動作確認が行えたり、エラー時にエラー箇所をバッチリ表示してくれたりと、今まで無駄になっていた時間を削ってくれるのもすばらしい。
でも、Java初心者の人がこれを使って何かを作るのはおすすめしない。Javaの作法をあえて無視して作られているから、それがクセになると後でいろいろ困ると思うよ。
とはいえ何も無いのも寂しいのでいくつかメモを…
もくじ
ストアドを呼び出す
CallableStatement でストアドを呼び出したいから Connection が欲しいよーという場合。
- application.conf で
db=mysql:user:pwd@database_name
を使わずdb.url
~db.pass
を設定する。その際db.url
の引数にnoAccessToProcedureBodies=true
を追加。 DB.getConnection()
で java.sql.Connection を取得する。
これで Connection を取得して、あとは Connection#prepareCall(ほにゃらら) で CallableStatement を作成すればいい。もちろん play.db.jpa.Model を継承したクラスもちゃんと使えるのでご心配なく。
Cacheクラスについて
前述のドキュメントを読むとわかる通り、Playにおいて「Session」と呼んでいるものは実際には「Cookie」のことだ。サーブレットでいう「Session」に近いものは「Cache」になる。正しくは違うものだけどサーバサイドで情報を保持するという観点から見れば似たようなもの。
ところが、デフォルト設定では Cache クラスを使用して保存したデータは JVM のメモリ上に保存されてしまう。Play はステートレスを売りにしているフレームワークなのにこれじゃサーバの分散化に対応できず台無し。
しかし、Play は Memcached に対応していた。なんと設定ファイルに Memcached の設定を記述する(というかコメントアウトを外す)だけで、コードはそのままに Cache を Memcached に保持することができてしまうのであった。めでたし。
ところが、コーディング次第ではJVMで動いてもMemcachedで動かないこともある。
1 2 3 4 5 6 7 8 9 10 11 |
String key = "key"; String ID = IDを取得する処理(); Set<String> set = Cache.get(key, Set.class); if (set == null) { set = new HashSet(); Cache.set(key, set); } if (!set.contains(ID)) { 処理(); set.add(ID); } |
IDの重複チェックをして、重複していないときのみ処理を行うというようなロジック。デフォルトの JVM 上にキャッシュする場合は正常に動くのに、Memcached を使用する設定にすると何度通しても三行目でカラの Set が取れてしまう。一度目(nullの場合)は6行目でちゃんとセットしているしなぁ。二度目は何かしら add された Set が取れるはず。
これを…
1 2 3 4 5 6 7 8 9 10 11 12 |
String key = "key"; String ID = IDを取得する処理(); Set<String> set = Cache.get(key, Set.class); if (set == null) { set = new HashSet(); //Cache.set(key, set); } if (!set.contains(ID)) { 処理(); set.add(ID); } Cache.set(key, set); |
このように、インスタンスに変更を加えたら改めてCacheにセットするようにしないといけない。これでちょっとつまづいたのでメモしておく。
また、開発モード(application.mode=dev
)で動かしている際、コードに変更を加えて保存するとすぐに反映されるから便利なんだけど、Memcached を使う設定にしていると Cache を取り扱うところでこけることがある。
1 2 3 4 5 6 7 8 9 10 |
play.exceptions.JavaExecutionException: Shutting down at play.mvc.ActionInvoker.invoke(ActionInvoker.java:290) at Invocation.HTTP Request(Play!) Caused by: java.lang.IllegalStateException: Shutting down at net.spy.memcached.MemcachedClient.checkState(MemcachedClient.java:240) at net.spy.memcached.MemcachedClient.addOp(MemcachedClient.java:255) at net.spy.memcached.MemcachedClient.asyncGet(MemcachedClient.java:729) at play.cache.MemcachedImpl.get(MemcachedImpl.java:92) at play.cache.Cache.get(Cache.java:172) ... |
こういうときは Play のサーバを再起動すると直る。めんどくさいので開発中は Memcached を使うのはよそう。
Eclipse 使用時の CRUD と Secure のコンパイルエラー回避について
Playでは、CRUD や Secure という機能を使用することができる。使用方法はそれぞれのページにあるようにすればいいだけなのだが、その通りにやっても Eclipse 上でエラーが出てしまう。
たとえば以下のようなクラスを書くと「Secure」「CRUD」の部分でエラーと判断される。
1 2 3 4 5 6 |
package controllers; import play.mvc.With; @With(Secure.class) public class Hoges extends CRUD { } |
Eclipse上でエラーが出ているだけで動作には問題ないけど気持ち悪いし、サーバを起動するたびにダイアログが出てうっとーしいのでやはりエラーは消しておきたい。
こういう場合は、プロジェクトに対して再度 play eclipsify
を行うことで、ソースがリンクされてコンパイルエラーが解消される。
CRUD使用時の注意
CRUDを使おうとしてしばらく迷ってしまったことがあって、あまり目にしないことだとは思うが一応メモ。
CRUDは「モデルクラスの複数形」となるクラス名をつけること、とある。ここで、「Message」というモデルクラスが既にあった場合、このテーブル用のCRUDクラスは以下のようになる。
1 2 3 4 |
package controllers; public class Messages extends CRUD { } |
ところが、この状態で起動するとエラーが発生してしまう。原因は「Messages」というクラス名。CRUD クラスの内部で「play.i18n.Messages」を使用しようとして、上記の Messages を呼んでしまうためエラーが発生していた。(完全修飾しないで呼んでるから)
なので、そういう場合は仕方なく…
1 2 3 4 5 6 |
package controllers; import models.Message; @CRUD.For(Message.class) public class Msgs extends CRUD { } |
このように、CRUD クラス名をぶつからないものに変更して、対象のモデルクラスを明示的に指定する必要がある。
ちなみに、CRUD や Secure のクラスは、ドキュメントでは「controllers」パッケージ直下に置くようになってるけど直下でなくてもいい。controllersの下に適当にパッケージ切って置けば動いてくれる(controllers の外側に置いても動くかどうかは不明)。Model に至っては models 以下でなくとも、どこに置いてもいい。あと、CRUD でビューをカスタマイズするときは app/views 以下の階層をパッケージと合わせること。
カスタムテンプレートタグ
カスタムテンプレートタグは、ドキュメントによると #{hello /}
などと書くと「app/views/tags/hello.html」(拡張子はxmlだったりjsonだったりいろいろ)を見に行く、とあり、実際その通りに動く。
では tags の中をフォルダ分けしたい場合はどうすればよいのかというと、「app/views/tags」をルートに見立て、フォルダの階層をパッケージのようにピリオドでつなげて記述する。つまり、
app/views/tags/sample/html/hello.html
を読み込みたい場合は
#{sample.html.hello /}
と記述する。
OAuth関連
Twitter と連携するアプリを作ろうとしたときのこと。同梱のサンプル通り作ってみたんだけど、認証はうまくいくもののどうも投稿の部分でコケてしまう。
1 |
String response = WS.url(url).oauth(TWITTER, getUser().getTokenPair()).post().getString(); |
Response を見ると「Incorrect signature」とかいうメッセージが。クエリも文字化けしている様子。
半角英数字だけ入力すると通るのでエンコードの問題かなー、などと一時間以上試行錯誤したけど結局ダメだったので、play.libs.WS を使うのを諦めて oauth.signpost~ を直に使うことにした。
1 2 3 4 5 6 7 8 9 10 11 12 |
OAuthConsumer consumer = new DefaultOAuthConsumer(consumerkey, consumersecret); consumer.setTokenWithSecret(accesstoken, accesssecret); URL url = new URL("http://api.twitter.com/1/statuses/update.xml?status=" + URLEncoder.encode(status, "utf-8")); HttpURLConnection request = (HttpURLConnection) url.openConnection(); request.setRequestMethod("POST"); consumer.sign(request); if (request.getResponseCode() != HttpURLConnection.HTTP_OK) { // エラー処理 } |
一発でうまくいく。なんじゃそりゃ!
追記
※1.2 では TokenPair が deprecated になっている。また、play.libs.WS での送信でクエリの文字化けが起きなくなった(1.2.5で確認)。
これらを踏まえて Twitter API 1.1 に対応したソースは以下。ほぼサンプル通りになった。
1 2 3 |
String url = "https://api.twitter.com/1.1/statuses/update.json?status=" + URLEncoder.encode(status, "utf-8"); HttpResponse response = WS.url(url).oauth(serviceInfo, accesstoken, accesssecret).post(); ... |
eclipse使用時のランチャにフレームワークIDを振る
play new xxx
→ play ec xxx
で作成したeclipseプロジェクトにあるランチャ「xxx.launch」は play id yyy
であらかじめ割り当ててあるフレームワークIDで起動するようになっているが、手動でフレームワークIDを書き換える場合はこれをエディタで開き
-Dplay.id=yyy
の部分を任意の値で書き換えれば良い。