- ブログ
- Wicket
- SnipSnap
4/8/08
祭に参加するためにさわったとか、ないわー
1/27/08
Twitter を使っていて、特定のメッセージを投稿できなかった経験はありませんか? これまで NG ワードかと思っていたのですが、僕が遭遇した問題に関して言えばコマンドの解釈のされ方によるもののようです。
のように使います。web と IM ではこんなふうに表示されます。
ややこしいのは Twitter はコマンドが文頭になくても、英数字 (と一部の記号 ※2) 以外を無視してコマンドと認識してしまうのです。たとえば上の例の "こんにちはw" は w コマンドと解釈されます。web から "こんにちはw akr" とやってみてください。※2 調べた範囲では ! が該当します。上の例の顔文字の方はというと、* (アスタリスク) がコマンドです。
とすると @akr の最後の発言を favorite に入れます。
API にコマンドを解釈しないオプションを要望しています。(http://getsatisfaction.com/twitter/topics/request_for_ignore_commands_feature_for_api_statuses_update) ぜひ "I like this idea!" をクリックしてください。(下の方にある黄色のボタンです)
どんなメッセージを投稿できないの?
たとえば次のものは発言できません。- こんにちはw
- ヾ(・ω・*)ノ
投稿できないときはどうなるの?
web や IM から投稿するとエラーメッセージが表示されます。こんな感じ。- Mysteriously Unnamed, since Aug 2006.
- You have made 's latest update a favorite.
じゃあ原因は?
結論から言うと Twitter のコマンドが影響しているようです。Twitter ではメッセージを入力するところに書けるコマンドがいくつか用意されています。本家のヘルプはこちらです。(http://twitter.com/help/lingo)ここにあるのが全てではなく他にもあります。どうやら半角の w や * (アスタリスク) もコマンドのようなのです。w は whois コマンド (本家ヘルプには書いてありませんが) の短縮系でw akr
akr, since Apr 2007. bio: 犬好きの Java 好きのピンボール好きです。
* akr
- ヾ(・ω・*)ノ
どうすれば投稿できるの?
コマンドに解釈されなければ投稿できます。方法はふたつあります。- w や * の前に英数字か記号を書く
- コマンド文字を複数個入れる
まとめ
投稿できない問題は、コマンドと解釈されてしまうのが原因のようです。特に w は <日本語>w の形式になることが多く投稿できないパターンにハマりやすいです。困るのが API クライアントです。上に書いたように API ではエラーを完全に知ることができません。最近 API クライアントで投稿できないという話をちょくちょく聞きますが、ほとんど (全て?) がこの問題に該当しているのではないかと思っています。クライアント側での対応が必要かもしれませんね。API にコマンドを解釈しないオプションを要望しています。(http://getsatisfaction.com/twitter/topics/request_for_ignore_commands_feature_for_api_statuses_update) ぜひ "I like this idea!" をクリックしてください。(下の方にある黄色のボタンです)
12/25/07
Twitter クライアント 「夏ライオン」についてはこちら (http://www.physalis.net/ss/space/NatsuLion) をどうぞ。
10/28/07
このブログは以前は SnipSnap (http://snipsnap.org) で動かしていたのですが、今は SnipSnap のコンセプトを参考にして Wicket + Spring + Hibernate で作った自作のシステムで動かしています。Wicket でシステムを作りたかったのもありますが、SnipSnap の更新が遅かったのが大きな理由です。そんな SnipSnap も今年の 6 月に開発終了がアナウンスされ、有志で開発を継続する体制が整いつつあるところです。これまでの経緯をまとめてみたいと思います。
- SnipSnap は Leo と Stephan の二人がコミッタとして開発を続けていました
- 彼等以外がパッチを送って反映する体制になっていなかったのと、二人の開発スピードが落ちていったことで、やがて開発者が離れていく傾向にありました
- 2007/06/29 に開発の終了が発表されました。(http://snipsnap.org/comments/start/2007-06-29/1)
- 同じエントリのコメント欄にて aos さんが中心になって SourceForge で開発しようぜという話になりました (僕だけ自分のサイトの宣伝してるんで申しわけないw。いやほんと公開するつもりなんですよー)
- 実は以前にもユーザのつぎあげがあって SourceForge にプロジェクトが作られたことがあるのです。SnipSnap on SF 残念ながら全く更新されず死にプロジェクトになっています
- 管理者は SnipSnap のコミッタ二人なのですが、彼等に連絡が取れないのでプロジェクトにアクセスすることができませんでした
- 仕方なく他の名前でプロジェクトを立ち上げることになり、aos さんが SnipSnip プロジェクトを立ち上げました (スニップス *ニ* ップです)
10/21/07
今回は An Easier Java ORM Part 4 の Pluggable Name Converters を見ていきます。テーブルとエンティティクラスのマッピングの話題です。名前のマッピング方法はデフォルトでは Camel Case 方式 (実際には先頭を小文字にするだけ) です。例えば BillingAddress クラスなら billingAddress テーブルが作成されます。@Table アノテーションを使って明示的にテーブル名を指定することもできます。
名前変換は TableNameConverter を実装するクラスが担当していて、取り替えができる (pluggable) ようになっています。EntityManager に対して setNameConverter で実装クラスを渡してやります。元記事には、自分で作る場合は AbstractTableNameConverter を継承するのがお勧めとありますが後述する PluralizedNameConverter に特化したような処理が含まれるので、自前で TableNameConverter を実装した方が手っ取り早いと思います。その場合、上記の @Table の処理を実装しておいた方がいいでしょう。下記のコードを含むだけで良いです。English Pluralization
ActiveObjects にはデフォルトの CamelCaseTableNameConverter の他に英単語の複数形を考慮に入れた PluralizedNameConverter が用意されています。語尾に s を付けるだけでなく Person -> people のような特殊なものまでサポートされています。試してみました。こんなテーブルが作成されました。サボテンクラスってのも変な話ですが、ちゃんと変換されてることが確認できました ;-p実装としては変換ルールを書いたプロパティファイルを使って変換しています。net/java/ao/schema/englishPluralRules.properties を見れば変換の仕組みがわかると思います。
@Table ("PERSON_TBL") public class Person { … }
manager.setNameConverter(new MyNameConverter());Table tableAnnotation = entity.getAnnotation(Table.class); if (tableAnnotation != null) { return tableAnnotation.value(); }
em.setNameConverter(new PluralizedNameConverter());
em.migrate(Cactus.class);CREATE TABLE cacti (
id INTEGER AUTO_INCREMENT NOT NULL,
name VARCHAR(45),
PRIMARY KEY(id)
) ENGINE=InnoDB10/15/07
今回は An Easier Java ORM Part 4:http://www.codecommit.com/blog/java/an-easier-java-orm-part-4 の Schema Generation の項を読んでみます。(というかあまり本文に沿ってないけど、、、まとめてみます)
この例で getter と setter の両方に同じアノテーションが付いているのがわかると思います。これは java.lang.Class.getMethods() の戻り値配列で先に現われる方のアノテーションだけを使う仕様になっているためです。Java 仕様では戻り値の順序は保証されていません。したがってアノテーションは getter と setter の両方に同じものを付ける必要があります。(もしくは片方に @Ignore を付けます)アノテーションの種類には以下のものがあります。
スキーマ生成
ActiveObjects にスキーマ (DDL) を作成してくれる機能があります。ちゃんと関連を考慮して外部キーを作ってくれるのに加えて、アノテーションでいろいろと指示することができます。次のコードが例として挙げられています。public interface Person extends SaveableEntity { public String getFirstName(); public void setFirstName(String firstName); @Unique @SQLType(precision=128) public String getLastName(); @Unique @SQLType(precision=128) public void setLastName(String lastName); @SQLType(Types.DATE) public Calendar getBirthday(); @SQLType(Types.DATE) public void setBirthday(Calendar birthday); @Accessor("url") public URL getURL(); @Mutator("url") public void setURL(URL url); }
- Mutator
- Accessor
- OneToMany
- ManyToMany
- Ignore
- SQLType
- PrimaryKey
- NotNull
- Unique
- AutoIncrement
- Default
- OnUpdate
Accessor/Mutator
ActiveObjects は getXXX や isXXX または setXXX が getter、setter を自動的に判別しますが、これらのアノテーションにより明示的に getter setter を指定することができます。また getXXX などの場合でもデータベースの列名が意図通りにならないことを避けるために用います。上記の getURL がこの例で、@Accessor を付けないと デフォルトの名前マッピングアルゴリズムでは uRL という列名になってしまいますので url を明示的に指定しています。SQLType
Java <-> SQL の型マッピングは自動的に解決されます。(マッピングは java.ao.types 以下で定義されています。) 自動のマッピングが不適切な場合や precision や scale まで指定したいときに SQLType アノテーションで明示的に指定することができます。 value には java.sql.Types の値を指定します。@SQLType(value=Types.INTEGER, precision=5, scale=0)
int getCount();OnUpdate
更新時に設定される値を指定します。MySQL では ON UPDATE、Oracle の場合はトリガとして定義されます。 MySQL の場合は指定できるのは次のいずれか。- CURRENT_DATE
- CURRENT_TIMESTAMP
10/1/07
それでは今日は An Easier Java ORM Part 3 (http://www.codecommit.com/blog/java/an-easier-java-orm-part-3) を読んでみます。この回のネタは
ブログエントリには execute() のかわりに executeConcurrently() を実行しているサンプルもありますが、このメソッドはバージョン 0.5 にはありません。(どこで存在したか不明。ただの例かな?)今回はここまで。
- サポート対象データベース
- トランザクション
サポート対象データベース
正式サポートはこちら。- Derby (standalone and embedded)
- MySQL
- PostgreSQL
- Oracle (with the limitation that @AutoIncrement is only supported on primary keys)
- Microsoft SQL Server (Microsoft and JTDS providers)
- HSQLDB (standalone and embedded)
トランザクション
では続いてトランザクション。トランザクションは net.java.ao.Transaction クラスの run を実装して定義します。Transaction#execute() は下記の処理を実行します。run メソッド内が一つのトランザクションになります。- getConnection()
- run() の中を実行
- commit()
new Transaction(manager) { public void run() { Account david = getEntityManager().get(Account.class, 1); david.setValue(david.getValue() - 1000); david.save(); ? Account mary = getEntityManager().get(Account.class, 2); mary.setValue(mary.getValue() + 1000); mary.save(); } }.execute();
9/27/07
今日は An Easier Java ORM Part 2 (http://www.codecommit.com/blog/java/an-easier-java-orm-part-2) を読んでみます。内容は SaveableEntity と Implementations です。
下記のコードで setter を 2 つ呼んでみます。結果はこうなります。
UPDATE 文は 1 回だけ、save() の段階で実行されます。
対応する実装クラスを書きます。Utilities は自前のユーティリティクラスです。(適当に作ってください)実行はいつもどおりに。最後の getPassword() で MD5 でハッシュ化された値が取得できます。このあたりは下記要領で行われます。
SaveableEntity
SaveableEntity はバージョン 0.5 でなくなってます。理由はこっちのエントリ SaveableEntity Bids a Fond Farewell (http://www.codecommit.com/blog/java/saveableentity-bids-a-fond-farewell) に書いてあります。要は普通 SaveableEntity を使うからそれをデフォルトにしたってことのようです。バージョン 0.5 より前だと Entity の setter を呼ぶたびに UPDATE 文が発行されていたようで、SaveableEntity を使えばそれらを一回の UPDATE 文にまとめることができたとのこと。0.5 以降の Entity はこの動作になりました。ブログエントリにあるサンプルコードで言えば、こんな Entity があってpublic interface Person extends Entity { public String getFirstName(); public void setFirstName(String firstName); public String getLastName(); public void setLastName(String lastName); }
private void initializePerson(EntityManager em, String name) throws SQLException { String[] names = name.split(' '); Person p = em.create(Person.class); p.setFirstName(names[0]); p.setLastName(names[1]); p.save(); }
INSERT INTO person (id) VALUES (DEFAULT) UPDATE person SET firstname = ?,lastname = ? WHERE id = ?
Implementations
では続いて Implementations。ActiveObjects ではエンティティをインタフェースとして宣言するので、そのままではエンティティ自体にロジックを持たせることができません。そこで実装クラスを明示的に指定する機能が用意されています。@Implementation アノテーションで指定します。ブログエントリのサンプルを見てみます。User クラスの setPassword() にクリアテキストを渡すと MD5 のハッシュがデータベースに登録される仕掛けです。まず Entity の宣言。(ブログエントリの方は extends SaveableEntity になってますが、前述の通りなくなったので Entity に書きかえています)@Implementation(UserImpl.class) public interface User extends Entity { public String getUsername(); public void setUsername(String username); public String getPassword(); public void setPassword(String password); }
public class UserImpl { private User user; public UserImpl(User user) { this.user = user; } public void setPassword(String password) { user.setPassword(Utilities.md5sum(password)); } }
// …
User u = em.create(User.class);
u.setUsername(“daniel“);
u.setPassword(“password“);
u.save();System.out.println(u.getPassword()); // prints the MD5 hashed value of “password“- EntityManager がエンティティインタフェースのプロキシを作成する
- @Implementation があれば
- エンティティのインスタンスを引数にして実装クラスのコンストラクタ (例: UserImpl(aUser) ) を呼ぶ
- init() があれば呼ぶ
- エンティティ (実体はプロキシ) のメソッドが呼ばれると
- 実装クラスに該当メソッドが実装されている場合はそれを呼ぶ。
- 実装クラスがなかったり、メソッドが実装されていない場合はプロキシ側で対応する。setter なら save() 時のデータベース更新のために値を保存しておく。getter なら保存されている値を返す
- エンティティのインスタンスを受け取るコンストラクタを用意する
- setter でエンティティの setter を呼んで値をセットしておく
9/25/07
しばらく時間があいてしまいました。これも全部 civ4 のせいです><)さて今日は前回に引き続き An Easier Java ORM (http://www.codecommit.com/blog/java/an-easier-java-orm) を見ていきます。今回は関連です。ActiveObjects で関連を表現するにはアノテーションを使うようです。それだけでなくメソッドの名前が重要みたいです。ActiveRecord と一緒ですね。サンプルに出ているのはコレ。Person 側に House への関連を示すメソッド(getHouse, setHouse)が定義されています。House 側も getPeople というのがありますね。Person の複数形で People です。@OneToMany アノテーションも付いてます。とにかく試してみます。住所を Tokyo とする House オブジェクトを作って Person akira, taro にそれぞれ setHouse します。実行すると MySQL には下記のデータが入ります。Person からは foreign key の houseID で House を参照してますね。このときのログはこちら。INSERT と UPDATE が実行されていますね。EntityManager#create 時に INSERT 文が実行され、Entity#save 時に UPDATE が実行されます。では今度は getPeople のテスト。さっき作った House のプライマリキーが 1 なので、それを指定して get します。そのあと getPeople してます。System.out.println の出力はこちら。これらは Person の Proxy が出力しています。ログ出力はこちら。ちゃんと動いてますね。House インタフェースの getPeople は ActiveRecord ならデフォルトではこの名前でなければいけなかったと思いますが、ActiveObjects ではどうなのかな? 試してみます。これでどうだ。エラーになるかなと思ったけど実際には結果は変わりませんでした。まぁシグネチャで判断できるしね。
public interface Person extends Entity { public String getFirstName(); public void setFirstName(String firstName); public House getHouse(); public void setHouse(House house); }public interface House extends Entity { public String getAddress(); public void setAddress(String address); @OneToMany public Person[] getPeople(); }
EntityManager em = new EntityManager(“jdbc:mysql://localhost/ao“, “akira“, “akira“);
Logger.getLogger(“net.java.ao“).setLevel(Level.FINE);
House house = em.create(House.class);house.setAddress(“Tokyo“);Person p1 = em.create(Person.class);
p1.setFirstName(“akria“);
p1.setHouse(house);Person p2 = em.create(Person.class);
p2.setFirstName(“taro“);
p2.setHouse(house);house.save();
p1.save();
p2.save();mysql> select * from house; +----+---------+ | id | address | +----+---------+ | 1 | Tokyo | +----+---------+row in set (0.00 sec)
mysql> select * from person; +----+-----------+---------+ | id | firstName | houseID | +----+-----------+---------+ | 1 | akria | 1 | | 2 | andy | 1 | +----+-----------+---------+ 2 rows in set (0.00 sec)
2007/09/25 0:57:01 net.java.ao.DatabaseProvider executeInsertReturningKeys 情報: INSERT INTO house (id) VALUES (DEFAULT) 2007/09/25 0:57:01 net.java.ao.DatabaseProvider executeInsertReturningKeys 情報: INSERT INTO person (id) VALUES (DEFAULT) 2007/09/25 0:57:01 net.java.ao.DatabaseProvider executeInsertReturningKeys 情報: INSERT INTO person (id) VALUES (DEFAULT) 2007/09/25 0:57:01 net.java.ao.EntityProxy save 情報: UPDATE house SET address = ? WHERE id = ? 2007/09/25 0:57:01 net.java.ao.EntityProxy save 情報: UPDATE person SET firstname = ?,houseid = ? WHERE id = ? 2007/09/25 0:57:01 net.java.ao.EntityProxy save 情報: UPDATE person SET firstname = ?,houseid = ? WHERE id = ?
House h = em.get(House.class, 1); for (Person p : h.getPeople()) { System.out.println(p); }
person {id = 1}
person {id = 2}2007/09/25 1:08:39 net.java.ao.EntityProxy retrieveRelations 情報: SELECT id FROM person WHERE houseID = ?
@OneToMany
public Person[] get人();9/19/07
ActiveObjects (https://activeobjects.dev.java.net/) に興味が出てきました。まだ情報がほとんどなくって、公式ページにあるサンプル dogfood blog は svn でアクセスするとパスワード聞かれるし、よくわかりません。どうやら作者さん(?) のブログ (http://www.codecommit.com/blog/) が詳しそう。
そこで、とっかかりとして、An Easier Java ORM (http://www.codecommit.com/blog/java/an-easier-java-orm) を読んでみます。(このエントリは ActiveObjects 0.5 を使って動作を検証しています)
要するに Rails の ActiveRecord みたいなものを Java で実装したのが ActiveObjects のようです。AR のシンプルで速く開発できる点を評価しているみたいです。AR の例としてこんなのが挙げられています。(元のブログより引用)僕は Rails の入門本を読んだくらいしか知りませんし Ruby もほとんどわからないのですが、Ruby には存在しないメソッドをトラップする機構があって、それで first_name なんていう未定義のメソッドをうまく処理できるとのこと。でも Java 使いからすれば型安全性だとかコンパイル時の静的チェックだとかが気にならなくもないです。で、上記のコードと同等のことを ActiveObjects でやるとこんな感じらしいです。(元のブログより引用)
クラス、メソッド定義をちょいはしょってるのでこのままじゃあ動きませんが、雰囲気はわかりますね。最初 4 行がエンティティ Person の宣言。ActiveObjects ではエンティティをインタフェースとして宣言します。普通の POJO (ってのも変な言いまわしか) みたいにクラスではありません。これには賛否あるようでコメント欄に書かれていますが、Java の Proxy の仕組みがインタフェースのみが対象とするためインタフェースにしているそうです。cglib 等を使えばクラスも対象にできるらしいので将来的にはクラスも使えたりするのかもしれませんね。また、Entity インタフェースを継承する必要があります。これについてもコメント欄でアノテーション (@Entity とか) を使わないのはなぜかとありますが、getID(), setID() なんかのメソッド宣言を継承するためとのこと。ポイントはこのへん。
それで次の em.get(1) ですが、これはたぶん間違い。少なくともバージョン 0.5 にこんなメソッドはありません。あるのはなので、
と書かないといけないです。あ、第 2 引数の 1 はプライマリキーです。
さて実行前にテーブルを作っておく必要があります。手動で作ってもいいけど自動でも作れます。ブログに書いてある Generator.generate() メソッドはバージョン 0.5 には存在しません。かわりに migrate() なら動きます。(ドキュメントに書いてあったわけじゃないので確証はないです)
(2007/09/20 追記)
EntityManager.migrate() の方が簡単みたいです。
実行すると下記のようなテーブルを作ってくれます。
あと、レコードを 1 行 insert しておきます。
それでは実行してみたいと思いますが、ActiveObjects が実行する SQL 文を表示させる方法があるので設定しておきます。ActiveObjects では Java Logging API を使って net.java.ao の Logger に対し FINE で出力してます。理由がわからなかったのですが、EntityManager の static イニシャライザで無効にしているらしく、EntityManager をロードして (使って) から改めて設定してやる必要があります。logging.properties
JVM 起動引数
ソースコード
無理矢理だけどこれでまぁ出力されるので良しとしよう。
(詳しくは EntityManager のリファレンスを参照してください)いよいよ実行です。getFirstName() の結果 'akira' が取得できました。ログにはこんなのが出力されます。なるほど。このブログエントリにはまだ続き (relation とか) があるけど、今日はこのへんで。なかなか面白そうだなー。
要するに Rails の ActiveRecord みたいなものを Java で実装したのが ActiveObjects のようです。AR のシンプルで速く開発できる点を評価しているみたいです。AR の例としてこんなのが挙げられています。(元のブログより引用)
class Person < ActiveRecord::Base endputs Person.find(1).first_name
public interface Person extends Entity { public String getFirstName(); public void setFirstName(String firstName); }EntityManager em = new EntityManager(jdbcURI, username, password);Person p = em.get(1); System.out.println(p.getFirstName());
- Entity を継承してインタフェースを宣言する
- インタフェースでは getter/setter を宣言する
EntityManager em = new EntityManager(“jdbc:mysql://localhost/ao“, “akira“, “akira“);public <T extends Entity> T get(java.lang.Class<T> type, int id)
Person p = em.get(Person.class, 1);
Generator.migrate(new MySQLDatabaseProvider(“jdbc:mysql://localhost/ao“, “akira“, “akira“), Person.class);EntityManager em = new ....
em.migrate(Person.class);CREATE TABLE person (
id INTEGER AUTO_INCREMENT NOT NULL,
firstName VARCHAR(45),
PRIMARY KEY(id)
) ENGINE=InnoDBinsert into person values (1, 'akira');
handlers= java.util.logging.ConsoleHandler java.util.logging.ConsoleHandler.level = ALL java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter .level = FINE net.java.ao.level = FINE
-Djava.util.logging.config.file=logging.properties
EntityManager em = new EntityManager(“jdbc:mysql://localhost/ao“, “akira“, “akira“);
Logger.getLogger(“net.java.ao“).setLevel(Level.FINE);SELECT firstName FROM person WHERE id = ?
