PlayFrameworkでローカルにあるJarを使いたい

こんな形でjarをローカルフォルダに突っ込んで、使用することができます。
こうしておくとJenkinsでも、ビルド前にdependencies --syncすることで、ちゃんと指定したjarを使ってくれます。
jarのファイル名は、artifactのところにあるように「モジュール名-バージョン.jar」の形に直しておく必要があります。

require:
    - play
    - play -> cobertura 2.4
    - provided -> dom4j 1.6.1
    - provided -> xmlbeans 2.3.0
    - provided -> poi 3.8
    - provided -> poi_ooxml 3.8
    - provided -> poi_ooxml_schemas 3.8
    - provided -> stax_api 1.0.1
    
repositories: 
   - provided: 
       type:       local 
       descriptor: "${application.path}/../[module]/conf/dependencies.yml" 
       artifact:   "${application.path}/jar/[module]-[revision].jar" 
       contains: 
         - provided -> * 

Source: https://groups.google.com/group/play-framework/browse_thread/thread/b54e4e25ae49161b

jQuery + Backbone + JasmineでBDD その1

jQuery + Backbone + JasmineでBDDを試してみます。元ネタはこちらのすばらしいブログです。
最初に断っておきますが、私はJasmine初体験でBDD、TDDの知識も本で読みかじった程度です。
ついでに言うと、jQueryもちょろっと触ったことがあるくらいです。

なぜこんな無謀な記事を書いているかといいますと、Jasmineを使ってBDDに関する知識を獲得しつつ、BackboneでMVCフレームワークに触れながら、JavaScriptオブジェクト指向を勉強している間に、jQueryでDOMの操作やセレクターの使い方を覚えられたらサイコーだと思ったからです。わからないことだらけで、いろいろなドキュメントを参照しながら進みます。「それは違う!」とか「こうしたほうがいいんじゃない?」などあれば、ぜひツッコんでいただければと思います。

今回サンプルとして作るのは電話帳アプリです。目標はSpineのサンプルにあるような感じです。
まず最初の一歩は必要なファイルをローカルにダウンロードします。

Jasmine
https://github.com/pivotal/jasmine/downloads

Underscore.js
http://underscorejs.org/

Backbone.js
http://documentcloud.github.com/backbone/

JQuery
GoogleのCDN
https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js

Jasmine standaloneのZipを解凍したら、Underscore.jsとBackbone.jsはlibディレクトリに入れます。
どうやらsrcフォルダにソースを入れて、specフォルダにはSpecを入れる模様です。
準備ができたら次の行をSpecRunner.htmlに追加します。

※読み込む順番を修正しました。最初にjQueryを読み込むようにしないと後でおかしなことになります。

<head>
...
  <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
  <script type="text/javascript" src="lib/underscore.js"></script>
  <script type="text/javascript" src="lib/backbone.js"></script>

ChromeでSpecRunner.htmlを開いてみるとデフォルトのSpec実行結果が表示されています。
ツールからJavaScriptコンソールもついでに開いておきます。

もともとあったspec/*Spec.js、src/*.jsをSpecRunner.htmlのHeadタグから削除して、ContactSpec.js、Contact.jsを作成します。
SpecRunnerのHeadタグ内にそれぞれ追加してRefreshしてみます。まっしろの画面でなにも表示されません。。

とりあえずサンプルのPlay.jsとPlaySpec.jsを参考にしながらSpecを追加してみました。
describeのところで何に関してのテスト(振る舞い?)なのか、二番目の引数にfunction(テストの中身)を渡してます。
contactはこのテストの中で使うモデルで、beforeEachメソッドの中にある内容は、これから追加するSpec(テスト)が実行される直前に、毎回実行されるようです。specが走る前の準備をするためのメソッドということでしょうか?

ContactSpec.js

describe("Contact", function(){
	//Specの中で使うContact
	var contact;
	//各Spec実行前に毎回実行される
	beforeEach(function(){
		contact = new Contact({});	
	});
});

Chromeで画面を更新するとデバッガにUncaught TypeError: Cannot call method 'suiteComplete' of undefinedエラーが表示されました。
どうやらspecがないのでJasmineに怒られている模様です。ContactSpec.jsにSpecを追加してみます。

expectの中には自分が評価したい値を入れて、toEqualにexpect(期待)する結果を入れます。
「expect(contact).toEqual(null)」だと「contactがnullであることをチェックしてね」ということになります。
ここではその逆をやりたいので、Googleで探してみたところ「not」を使うと反対のことをexpectするように指定できるとあったので、やってみました。

	it("is my first spec!", function(){
		expect(contact).not.toEqual(null);
	});

今度はSpecのエラーになってくれました。
Contactがないよ、ということです。

Failing 1 spec
1 spec | 1 failing
Contact is my first spec!.
ReferenceError: Contact is not defined

Contactのコードを追加していないので、Backbone.ModelとしてContact.jsにモデルを定義してみます。

Contact.js

var Contact = Backbone.Model.extend();


Passing 1 Spec
Contact
is my first spec!

初めてPassしました。これがBDD、TDDのRed,Green,Refactorサイクルというやつの入り口でしょうか。
次になにをするのかよくわからないので、getFullNameで氏名が返ってくるようなメソッドを追加してみます。

Backboneのモデルではmodel.set({name: value})がSetterでmodel.get("name")がgetterになっています。
beforeEachでsetを使ってModelにfirstName、lastNameをセットします。

続いて新しいSpecを追加します。
beforeEachでfirstNameとlastNameがセットされているので、getFullNameを呼べば氏名が取得できるはずです。

	beforeEach(function(){
		contact = new Contact();	
		contact.set({firstName: "Taro", lastName: "Yamada"});
	});
	//...略
	it("should generate fullname from first name and lastname", function(){
		expect(contact.getFullName()).toEqual("Yamada Taro");
	});

もちろんモデルになにも書いてないのでRedになりました。

Failing 1 spec
2 specs | 1 failing
Contact should generate fullname from first name and lastname.
TypeError: Object [object Object] has no method 'getFullName'

getFullNameメソッドをモデルに追加してみます。

Contact.js

var Contact = Backbone.Model.extend({
	getFullName: function() {
		return null;
	}
});

なんとなくエラーがマシになりました。

Failing 1 spec
2 specs | 1 failing
Contact should generate fullname from first name and lastname.
Expected null to equal 'Yamada Taro'.

Backbone.Modelのgetを使って取得したfirstName、lastNameを繋げて返します。

	getFullName: function() {
		return this.get("lastName")+" "+this.get("firstName");
	}

全部Greenになりました!
さて、ここからどうしようか…

Passing 2 specs
Contact
is my first spec!
should generate fullname from first name and lastname

PlayFrameworkのtemplate その2

Include

テンプレートの中で別のテンプレートを呼び出すにはincludeを使います。
includeされたテンプレートでは、親テンプレートで使用可能な値を使うことができます。

includeする親側での呼び出しは下のような形です。

#{include 'Books/bookDetail.html'}

呼び出されるほうのテンプレート。
親で有効な値を使う場合は普通に${model.property}みたいに使えます。

<label>${book.author}</label>

Tag

テンプレートに対してパラメータを渡す場合にはTagを使います。
Tagというとわかりにくいですが、単にテンプレートと同じものだけども、こちらはパラメータを渡せます。
IncludeされたテンプレートからTagを呼び出すこともできます。

まず/views/tags/フォルダの下にTag化したいテンプレートを入れます。
Tag用のテンプレートの中では、親から受け取ったパラメータの頭にはアンダースコアが付きます。
この例では親テンプレートが「propertyName」を渡しているので、Tagテンプレートでは「_propertyName」として受けとります。

/*/views/tags/tagBookDetail.html*/
<input type="text" name="${_propertyName}" value="${_propertyValue}" />
/*親テンプレート*/
#{tagBookDetail propertyName:"author", propertyValue:"Goethe" /}

パラメータはカンマ区切りで渡します。

PlayFrameworkのtemplate

今日はPlayのテンプレートで3つ発見があったので、そのときのことをメモします。
ソースが手元にないので覚えてる部分から書いていきます。

・別のテンプレートをページの一部に読み込む(Railsのpartialみたいな)
・ループの中でインデックスを取得する
・フォームから複数のオブジェクトをListとしてコントローラーで受け取る

別のテンプレートを読み込むには、親になるテンプレートでincludeを使います。
今回やったのはこんな感じです。
1.EditAll画面でBookオブジェクトのListを受け取る。
2.BookのListをループさせて、includeしたBookオブジェクトの詳細を表示するbook.htmlで表示

/*editAll.html*/
#{form @Book.saveAll() , id:'saveAllForm'}
  #{list items:books, as:'book'}
    #{include 'book.html' /}
  #{/list}
<input type="submit" value="Save All" />
#{/form}
/*book.html*/
<input type="text" name="books[${book_index}].name" value="${book.name}" />
<input type="text" name="books[${book_index}].price" value="${book.price}" />

includeだと親のテンプレートで使える変数はすべてそのまま使えます。
book.htmlはループの中でincludeされているので「オブジェクト名_index」でインデックスを取得できます。
他にも、Listの最後のアイテムかどうか、最初のアイテムかどうかを判断するbook_isLastとbook_isFirstが使えます。

SubmitされたあとはController上のActionで、引数にListで指定するだけで使えます。

public static void saveAll(List<Book> books){
...
}

Source:
http://www.playframework.org/documentation/1.2.3/tags#list
http://stackoverflow.com/questions/7659310/play-framework-how-can-i-pass-collection-to-action-create

PlayFrameworkでTDDのマネをしてみた

自分用の小さな開発案件にPlayを使ってTDDを真似てみようと思いました。
とりあえずModelのテストからと書き始めたところ…

//Model
@Entity
public class Company extends Model{

}

//Test
public class CompanyTest{

    @Test
    public void testSomething(){
        Company c = new Company();
        assertNotNull(c);    
    }
    
}

これだけでも@TestアノテーションがあればJUnitでテストを実行することはできます。
ただ、これだとfindAllなどのJPAJava Persistence API)の機能を使うと下記のエラーが発生します。

UsupportedOperationException occured : Please annotate your JPA model with @javax.persistence.Entity annotation

エラーメッセージだけを読むと@Entityを使えとしか書いていないので、「ちゃんと@Entityってあるじゃん!?」と少しはまりました。原因はJPAの機能を利用する側(テスト用のクラス)がPlayFrameworkに対応していないとダメなので、きちんとplay.test.UnitTestをextendsしておく必要がありました。最初からドキュメントを読んでおけばこんなことにはならないです。。

import play.test.*;
public class CompanyTest extends UnitTest{
//...
}

Fixtureからデータを読み込むには、testパッケージの配下にあるdata.ymlにYAML形式でデータを追加してからFixtures.loadModulesで読み込む必要があります。

# Test data
Company(google):
   name:    Google
 
Company(zen):
   name:    Zenexity
@Before
public void setUp(){
  Fixtures.deleteAll();
  Fixtures.loadModels("data.yml");
}

http://ja.wikipedia.org/wiki/Java_Persistence_API
http://www.playframework.org/documentation/1.2.3/test

YUM, RPMのメモ

Redhat系のOSの場合、yum, rpmでパッケージの管理をすることになります。
削除や追加もたまに発生するのですが、毎回コマンドを忘れてしまい、その度に調べることになるのでメモします。

YUM

すでにインストールされているパッケージを調べるにはlist installedを使う。
ただこれだとインストール済みパッケージがすべて表示されるので、grepと組み合わせて、自分が探しているパッケージのみを表示する

# yum list installed | grep java
java-1.6.0-openjdk.i386                 1:1.6.0.0-1.25.1.10.6.el5_8    installed
sun-javadb-client.i386                  10.6.2-1.1                     installed
sun-javadb-common.i386                  10.6.2-1.1                     installed
sun-javadb-core.i386                    10.6.2-1.1                     installed
sun-javadb-demo.i386                    10.6.2-1.1                     installed
sun-javadb-docs.i386                    10.6.2-1.1                     installed
sun-javadb-javadoc.i386                 10.6.2-1.1                     installed

yumでインストールしたパッケージを削除する場合はremove

# yum remove PACKAGE


RPM

インストールの場合は-ivhオプション

rpm -ivh path_to.rpm

アップグレードの場合は-Uvhオプション

rpm -Uvh path_to.rpm

同じくrpmでインストール済みのパッケージを表示するには-qaオプションとgrepを組み合わせて使う

# rpm -qa | grep java
sun-javadb-common-10.6.2-1.1
sun-javadb-docs-10.6.2-1.1
sun-javadb-core-10.6.2-1.1
sun-javadb-demo-10.6.2-1.1
sun-javadb-javadoc-10.6.2-1.1
sun-javadb-client-10.6.2-1.1
java-1.6.0-openjdk-1.6.0.0-1.25.1.10.6.el5_8

さらにパッケージの詳細を確認する場合はqiオプション

# rpm -qi sun-javadb-client-10.6.2-1.1
Name        : sun-javadb-client            Relocations: /opt/sun
Version     : 10.6.2                            Vendor: Sun Microsystems, Inc.
Release     : 1.1                           Build Date: Wed 03 Nov 2010 04:19:39 PM JST
Install Date: Mon 21 May 2012 05:47:38 PM JST      Build Host: jdb-lin-i586
Group       : Applications/Databases        Source RPM: sun-javadb-client-10.6.2-1.1.src.rpm
Size        : 528518                           License: Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.  Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
Signature   : (none)
URL         : http://www.sun.com
Summary     : Java DB client
Description :
Client for Java DB

削除する場合には-eオプションを使う

# rpm -e PACKAGE

Source:
http://linux.kororo.jp/cont/intro/yum.php
http://www.atmarkit.co.jp/flinux/rensai/linuxtips/050inforpm.html
http://www.atmarkit.co.jp/flinux/rensai/linuxtips/049instrpm.html

JenkinsでVerifyError

新しく仲間に加わったJenkins氏がPlayのプロジェクトのテスト実行時にエラーを吐いていた。
APIをみても「some sort of internal inconsistency or security problem」といまいちはっきりしないのですが。

Execution exception VerifyError occured : Expecting a stack map frame in method ...

application.confでJAVAのバージョンを指定することで解決できた。

java.source=1.6

http://docs.oracle.com/javase/7/docs/api/java/lang/VerifyError.html
http://stackoverflow.com/questions/6704169/verifyerror-expecting-a-stack-map-frame-in-method-controllers-securesecurity-a
http://java.dzone.com/articles/javalangverifyerror-expecting