2011年1月26日水曜日

Custom XmlAdapter for Joda Time with xjc

JAXBを使ってますか?
JAXBとJoda-Timeを連携してみましょう。

以下のクラスとXMLをBindするのが目標です。
public class Foo {
  private DateTime bar;
  private DateTime baz;
}
<foo>
  <bar>2011-2-3</bar>
  <baz>2010-3-4T05:06:07</baz>
</foo>

JAXBでは面倒な点がいくつかありました。
1. @XmlType(propOrder)にフィールドを列挙する必要がある。
2. xs:dateはXmlGregorianCalendarに割り当てられている。

うーん、面倒ですね。GroovyだったらとかClojureだったらとか、思わずにはいられない。
それぞれの対策を書いていきます。

1. @XmlType(propOrder)にフィールド名を列挙する必要がある。
アノテーションでは順序が保たれないため、フィールドとpropOrderの双方に書く必要がある。
とりあえず生成するGroovyコードを書いたのですが、調べたらxjcなんてものがあったので、移植してみました。
かんたんなスキーマを書いてみます。
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="foo">
    <xs:complextype>
      <xs:sequence>
        <xs:element name="bar" type="xs:date" />
        <xs:element name="baz" type="xs:dateTime" />
      </xs:sequence>
    </xs:complextype>
  </xs:element>
</xs:schema>
生成されたbar,bazの型は、見慣れないXmlGregorianCalendar なんだこれ。

2. xs:dateはXmlGregorianCalendarに割り当てられている。
http://weblogs.java.net/blog/kohsuke/archive/2006/03/how_do_i_map_xs.html
xjcの開発者であるkohsukeのブログでXmlGregorianCalendarを使わない方法があったので参考にしました。
<xs:schema elementFormDefault="qualified" version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" jaxb:version="2.0" targetNamespace="calendar-schemalet">
  <xs:annotation><xs:appinfo>
    <jaxb:globalBindings>
      <jaxb:javaType name="java.util.Calendar" xmlType="xs:date"
        parseMethod="javax.xml.bind.DatatypeConverter.parseDate"
        printMethod="javax.xml.bind.DatatypeConverter.printDate"
        />
    </jaxb:globalBindings>
  </xs:appinfo></xs:annotation>
</xs:schema>

なるほど。変換するメソッドを呼び出すことができるわけですね。
この例では準備されたメソッドですが、任意のメソッドを呼び出すことができます。
標準のはCalendarなので、java.util.Dateに変換するのを書けばいいだけ。
簡単ですね。
これでどんな型でも自由自在。
xs:appinfoを別のファイルに移し、xs:includeすると共通化できて便利です。
うまく名前空間を利用するとインクルードなしでもいけるんでしょうが。

ここで一つ別の問題が出てきました。

3. 余計なAdapterが生成される
Adapter1やAdapter2が勝手に生成されてしまいます。
できればこれらの名前も指定したいところ。
最終的にはこうなりました。
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
  xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc" jaxb:extensionBindingPrefixes="xjc" jaxb:version="2.0">
  <xs:annotation>
    <xs:documentation>custom datetime adapter</xs:documentation>
    <xs:appinfo>
      <jaxb:globalBindings>
        <xjc:javaType name="org.joda.time.DateTime" adapter="hikoz.xml.DateAdapter" xmlType="xs:date"/>
        <xjc:javaType name="org.joda.time.DateTime" adapter="hikoz.xml.DateTimeAdapter" xmlType="xs:dateTime"/>
      </jaxb:globalBindings>
    </xs:appinfo>
  </xs:annotation>
  <xs:element name="foo">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="bar" type="xs:date" />
        <xs:element name="baz" type="xs:dateTime" />
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>
Adapterはこんな感じ。
日付の形式も自由自在。ただしおすすめはできないけど。
読みたいこともあるよね。
public class DateTimeAdapter extends XmlAdapter<String, Datetime> {
 private static final DateTimeFormatter YMDHMS = DateTimeFormat.forPattern("yyyy/MM/dd HH:mm:ss");
 @Override
 public DateTime unmarshal(String v) throws Exception {
  return YMDHMS.parseDateTime(v);
 }

 @Override
 public String marshal(DateTime v) throws Exception {
  return YMDHMS.print(v);
 }
}

以下はテストコードと、xjcの実行。
Antタスクもあったんだけど、最近build.xmlを書いてないから構文を忘れた。
Gradleかわいいよ

@Test
  public void readwrite() throws Exception {
    StringWriter sw = new StringWriter();
    Foo foo = new Foo();
    foo.setBar(new DateTime(2011, 2, 3, 4, 5, 6, 7));
    foo.setBaz(new DateTime(2012, 3, 4, 5, 6, 7, 8));
    JAXB.marshal(foo, sw);
    StringReader sr = new StringReader(sw.toString());
    Foo foo2 = JAXB.unmarshal(sr, Foo.class);
    assertThat(foo2.getBar(), is(new DateTime(2011, 2, 3, 0, 0, 0, 0)));
    assertThat(foo2.getBaz(), is(new DateTime(2012, 3, 4, 5, 6, 7, 0)));
  }

  public static void main(String[] args) throws Exception {
    com.sun.tools.xjc.Driver
        .main("-no-header -extension -d src/test/java -p hikoz.xml src/test/java/hikoz/xml/foo.xsd"
            .split("\\s+"));
  }

2010年12月22日水曜日

Werkzeugのデバッガ

鹿に尻をかじられたりもしましたが、
三日間の奈良旅行で、8万歩以上も歩き、とても充実した休暇をすごしました。

Python Web フレームワーク アドベントカレンダー2010 のバトンがまわってきたので、Werkzeugの紹介をします。
http://werkzeug.pocoo.org/

Werkzeugはみなさんご存知のFlaskの作者であるArminが作ったWEBフレームワークです。
実はFlaskのベースでもあります。
単体で使っても便利ですが、非常によい機能が詰め込まれているため、各種フレームワークに組み込んで使う人が多いようです。
たとえば、django-extensionsでDjangoに組み込んで使う人も多いでしょう。
私も、どんなWEBアプリケーションでもほとんどの場合、開発時に組み込んでいます。
運用時にも使うケースは少なくありません。

丁寧なチュートリアルもついているのですが、なにぶん、生のWSGIが露出しているフレームワークなので、
分かりやすいとは言えません。
今回は、便利な機能の一部に焦点を合わせて解説します。

さて、解説前に少し準備しましょう。
まず、ごく小さなアプリを一つ書きます。
hello.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from werkzeug import Response, script

def app(environ, start_response):
    response = Response('Hello, World!')
    return response(environ, start_response)

action_runserver = script.make_runserver(lambda: app)
action_shell = script.make_shell(lambda: {})

if __name__ == '__main__':
    script.run()

pythonでライブラリを試すときはVirtualenvが便利です。
wget https://bitbucket.org/ianb/virtualenv/raw/eb94c9ebe0ba/virtualenv.py
python virtualenv.py ~/.virtualenvs/pyadvc
source ~/.virtualenvs/pyadvc/bin/activate
pip install werkzeug
python hello.py runserver

http://localhost:5000/にアクセスするとHello, World!と表示されるはずです。

デバッガ

JavaやC#ではステップ実行を使ってデバッグするケースがあると思います。
Pythonでもpdbを使ってステップ実行ができますが、ブレークポイントを設定するのが面倒ですね。
WerkzeugではアプリケーションがExceptionをWerkzeugに投げたのを捕まえて、WEB画面上でステップ実行をすることができます。
ためしに一つエラーを起こしてみましょう

def app(environ, start_response):
    a = 'Hello'
    response = Response('%a, World!'%a)
    return response(environ, start_response)
app関数を書き換えてもう一度起動してみましょう
するとおなじみのInternal Server Errorが出るはずです。
これではエラーの原因がわかりません。
ただ、コンソールでは
response = Response('%a, World!'%a)
 ValueError: unsupported format character 'a' (0x61) at index 1
というように表示されているので、エラーの推測はできるでしょう。
ここで、%aが正しくないことはわかったのですが、なにが正しいのでしょうか。
そこでデバッガです。

python hello.py runserver --debugger
という引数をつけて起動しましょう。
再度表示してみると、カラフルな画面が表示されるようになりました。

Traceback (most recent call last)

  • File "/home/spam/ws/python/wz/hello.py", line 7, in app

    response = Response('%a, World!'%a)

response = Response('%a, World!'%a)

と書かれた行をクリックしてみましょう。
これでソースが確認できます。

さらに、その行の右にコンソールっぽいアイコンがありますのでクリックします。
すると[console ready]と表示されました。
これはPythonのコンソールです。
たとえば 1 + 1とすると2と返答があるはずです。
さて、直したいのは
'%a, World!'%a
の部分です。
まず、dump()とすると、変数一覧が表示されます。

>>> dump()

Local variables in frame

a'Hello'
start_response
environ{'wsgi.multiprocess'False'SERVER_SOFTWARE''Werkzeug/0.6.2''SCRIPT_NAME''''REQUEST_METHOD''GET'  }

aに'Hello'が入っているのがわかりますね。

ここで、 '%a, World!'%a と入力すると先ほどと同じエラーが出ます。
a,b,cといろいろ試したり、マニュアルを確認したところ、%sが正しいとわかりました。
>>> '%s, World!'%a 'Hello, World!'


これで、正しい文がわかったので、ソースを書き換えて終了です。


その他、Context LocalsというJavaでいうThreadLocalを便利にした機能から、WEBアプリに必須なルーティング機能まで、すばらしい品揃えです。
そのWerkzeugの機能を丁寧にラップして、高速+多機能テンプレートエンジンであるJinja2と組み合わせたのがFlaskです。

ぜひ試してみてください。

次は@lirisさんお願いします。

2010年8月19日木曜日

Why Corel MovieWriter sucks

プライベートのDVDを作成していたんだけど、CorelのDVD作成ソフトであるMovieWriterがひどすぎて、怒りが収まらない。
たった一枚のDVD-Videoのために、友人たちが取ってくれた合計40本程度の動画を細かく繋いだところ、
重すぎて映像を選択するだけで3分待たされるのはまだ許す。
映像の再生もしてない、ただタイトルをマウスでクリックするたびになんの計算をしてるんだと考えると腹立たしいけど。

イライラを抑えながらなんとか最後のDVD出力までたどり着く。
出力開始40分後、今度は突然、異常終了する。
なんのエラーメッセージも吐かないから対応も取りづらい。
MP4からHDまで多様な形式をつないでいるので、相性の悪いフォーマットがあるのかなと、
とりあえずチャプターを減らして、正常に終了したところとかかった時間から、不具合のあるチャプターを特定する。
しかし、不具合のあるチャプターも、本来の3分の1程度の長さでDVDをつくれば正常に出力されてしまうので
特定が難しいのに加えて、テストに時間がかかる。

つないだ映像の本数が多いし、形式も多様だから難しいのだろうと気を使わせる時点で
すでに値段の価値もないソフトウェアですね。
最初から対応してないって分かっていれば全部一度ffmpegでmpegに統一するよ。
しょうがないから作れる範囲の短いDVDで出力し、あとから自分でインポートしなおせば、
形式も統一だし、ソース本数が減るから焼けるようになるはず。

しかし、ここでも問題が発生。
チャプターの数も30に満たない数しかないのに、チャプター数の限界に達したとして
DVD書き込み中に中断。

これも何度か繰り返して特定。
原因は、映像に暗転をいれていたところ、0秒のチャプターが存在していたため。
エラーメッセージ間違ってるよ。
そもそも映像変換中に気づいてくれよ。
DVD書き込み中に中断したらDVDが一枚無駄になるんですけど。

それまでは警戒して、ファイル出力にしていたけど、今回は映像の編集も変換もないので
問題がないだろうと、つい油断してDVD出力に設定し寝てしまっていた。
信じちゃいけなかった。

そして、数々の試練を乗り越えて、ついに一枚出力完了。
ここまで2週間もかかった。
しかし、すでに疑心暗鬼の私は安心をしない。
もちろん再生して確認する。
どこでどんなトラブルがあっても驚かないぞと思っていたけどまさかね。
なんとまあ、あきれてしまう。
自分で作ったものをインポートしたら音声が全て消えるのはひどすぎるないか?

一回出力するのに40分程度はかかるから、一晩なんてあっという間。
あんな重いソフトウェアでも一応映像の切り貼りだけは三日で終わったのにそこからが長すぎる。
結果として、5GのDVD一枚をまともにつくることができずに2週間経過。
これでBlu-ray対応なんて笑わせる。

ここまでの数々の問題でも使えないことが明白ですが、もっとひどい問題もある。
MTSという形式があります。
HD動画の形式だけど、その動画も読み込んで編集することができるのはすばらしい。
出力も成功するのですが、実際は内部のデコードで失敗していて、映像が乱れてそれ以降の映像はとび、音声がずれるという悲惨な状態。
これを成功として、再生するまでわからないようにするのは良識を疑う。

金返せじゃすまない。
2週間の睡眠時間とその間の体調不良をどうしてくれる。

2010年1月22日金曜日

Play framework is a full stack framework

Play frameworkで楽しんでます。
フルスタックとはテンプレート、データベース補助があるだけでは不足

clean url routing, test, fixture, message after redirect, background job, cache, i18n程度あたりがそろっていないとFullとは言えない時代だと思う。
Playはその点、完璧だろう。
reverseまで持ってるんだから、私は当然お気に入り。

ただ、欠点もある。

background jobがスレッドで走ってしまうので、重くなる。
Hibernateとは別れたい。
ほかには、先日書いた内容など。

やはり私はpublic fieldを積極的に使いたい。
メソッドではなくフィールド
redirect(BookPage.find, 1)とかでリダイレクトしたいし、
tx(BookService.reserve, 2)とかでトランザクションを開始したい。

Routingもフィールドを使えばリファクタリング効くしね。

メソッドをフィールド扱いできるScalaには期待しています。

2010年1月21日木曜日

ANA-Amex

JALが沈没したのでクレジットカードを整理することにしました。

次はANA-AMEXにします。
アメリカン・エキスプレス

12月までは大盤振る舞いだったようだけど、まだ大丈夫か

1. 100円で2マイル
3ヶ月間ポイントが倍

2. 入会2000+1000マイル

3. オンライン明細で1000マイル
ただし、入会時は紙にしておいて、入会したあとに変更すること

4. 公共料金や電子マネー、携帯などの引き落としでそれぞれボーナス
合計すると10000マイルの予定

5. Edyチャージでポイントがつく
ただし6ヶ月間

6. 年会費がAMEXにしては安い
5250円

スルガ銀行ANA支店

2010年1月15日金曜日

Hello, Play framework

Play frameworkを使ったことがある人はどれぐらいいるんだろうか。
こんなに面白いフレームワークとは思わなかった。
いや、フレームワークというより、プラットフォームです。

まえにJava on Railsなんて宣伝を見てしまったせいでスルーしていた。
たしかに「Rails使いに媚びてDjangoをJavaにポートした」と言えないこともないけど。
ためしてから宣伝してください。迷惑です。
見所はコマンドラインなんかじゃない。まあ、Python for Windowsがついてくるのは確かに面白いけど。
なんといってもJavaの使い方が革命的に面白い。

なんと表現すればいいのか。
「コンパイルさえ通せば、あとはまかせとけ」
といったところか。
staticメソッドを積極的に使い、記述をシンプルにすることが使命であるようだ。

まずはこの三つの機能を見てみます。
1. Bind an HTTP parameter to a Java method parameter
リクエスト引数をメソッド引数に名前で結びつけちゃいます。
メソッド引数名ですよ?
アノテーションなんて不要です。
まさしく名前つき引数なのです。

routesに
GET  /show/{name}  Application.show
とあったとして、
/show/Tom?page=2
にアクセスがあったならば
public static void show(String name, Integer page)
では、nameにTomが入り、pageは2になるのです。
型や順番や偶然を使っているわけではありません。
引数名を見ているのです。

2. Redirect to an action by calling the corresponding Java method
一度でもDjangoのreverseを使ったことがあれば、URLなんて直接書きたくないと思うことでしょう。
この場合ならばControllerのメソッド名で指定したいところ。
しかし、文字列でredirect(reverse("Books.list", page))では芸がない。
redirect(Books.list, page)と書くことができればいいけど、残念ながらコンパイルエラーになるでしょう。
Javaのメソッドとフィールドの名前空間と同じであれば、もっとまともなフレームワークを作れたのですけどね。
そこでPlayは突き抜けました。
Books.list(page)
と書けば勝手にリダイレクトされます。

staticメソッドだからといって気を抜いてはいけません。
フォワードではなく、間違いなくブラウザにResponseがかえります。

3. Don’t Repeat Yourself when passing Java objects to templates
Spring MVCではデフォルトの名前というものが存在します。
User => user, List<User> => userList
というように、型から名前をつけてくれるので、テンプレートに名前をつけながら渡す必要がありません。
しかし、Playでは1と同様に型ではなくローカル変数名を取得します。
ローカル変数名ですよ。
pythonでもrender(article=article, user=user)と書かなきゃいけないってのに。
bytecode enhanceってレベルじゃないですね。
ただ、残念ながら実行時に解決なので、いっそコンパイラを作って変数名をbytecodeに埋め込んでほしいと思います。


以上のようにJavassistの黒魔術を駆使して理想郷を追い求めます。

だがちょっと待ってほしい。
この黒魔術
まさにDIでやっていることそのままではないか。
DIの黒魔術を許してこれを許さないなんて、なんという傲慢。
フィールドに準備しておくか、staticで呼び出すかの違いしかない。
ルールを決めておけばOKとはどの口が言ったものか。
Mockitoを見て便利だといっていたじゃないか。
staticであればコードがそのまま追えるなんて、いつまで20世紀にしがみついているんだ。
もうそんな時代じゃないんだ。
EJB2信者なんて、もういないでしょ?

DIに飼いならされた開発者たちなら、ルールだと説得すればすぐ納得するね。
賭けてもいい。
逆に、「Proxyを使って実現できるDIとは違う」と反論するような人が相手なら、
メリットを比較してあげれば、いずれは「ひでえ」といって納得するはず。

残念なところもいくつか挙げておく。

1. アプリの構造が貧弱
Djangoのアプリを引き継いでほしかった。
一定以上の規模のものを作れるのだろうか。
心配です。
2. formが貧弱
ModelとはべつにFormクラスを作ってください。
ModelべったりのFormだと画面や権限に応じてValidationや項目を変えづらい。
3. Bytecode拡張される範囲が不明
Bytecode拡張される範囲が明確でないため、自分で拡張しようとすると
なかなか恐ろしい。
たとえば、もう少し複雑で動的なFormがあって、複数のControllerで共有したいとすると、
自分で独自のクラスを作りたくなるけど、どこまで拡張してくれるのか試すまで、
いや、試してもよくわからない。
returnをなくそうとしたり、Javaの言語仕様をとことん無視しようとしているので、
なかなか機能拡張は難しそう。

まとめ

二週間以内で作るものにはちょうどいいかも。
どうしてもJavaがいい。
Javaじゃなきゃいやと根拠なく言われたときに使うことを考えてもいいかもしれない。
Springの起動が遅くて、決別したいとは常々思っているので、
こういうアプローチが流行ってくれば面白いとは思っています。
Bytecode拡張以外は使いやすく、わかりやすい機能がてんこ盛りなのでお勧めです。
Bespinとうまく連携すれば、全文検索つきのCMSが結構簡単に作れるかも。

2009年12月16日水曜日

aws with python

botoがよろしい
boto
結構パッチを取り込んでもらったのでだいぶ使いやすくなった。

botoドキュメント

amiをEBSへ変換する
Amazon EC2 – Boot from EBS and AMI conversion

botoで変換するとこんな感じ
Creating an EBS-backed AMI from an S3-backed AMI

いろいろ自動化できて楽しい。
AMI内でサーバを再起動するより、裏で立ち上げてElastic IPをすりかえてしまうとか
いろいろ試せて楽しい
Spot Instanceにも、とりあえずだけど対応してもらった