Graphics Context

ドキュメントを流し読みして、なんとなく感じをつかんでみる。
イメージを描画するのは、なんとかContextと名前のついている「グラフィックスコンテキスト」だそう。最近は何かにつけコンテキストという言葉が出てきますねー。
グラフィックスコンテキストは

  • Bitmap Graphics Context
  • PDF Graphics Context
  • Window Graphics Context
  • OpenGL Graphics Context
  • Postscript Graphics Context

があるそうです。

今やりたい事はファイルを変換する事だから、画面に描画するようなWindow Graphics Contextなどは関係なさそう。さしあたり、Bitmap Graphics ContextとPDF Graphics Contextが関係しそうです。

処理の流れとしては、

  1. BitmapContextを用意する。
  2. PDFを読み込む。
  3. そのコンテキストにPDFの中身を描画する。

ってな具合で行けるのではないかと思われます。

ただ、PDFはページと言う概念があるので、

  1. 変換元のPDFが何ページあるか調べる。
  2. ページ数分だけ、処理を繰り返す。(そのページサイズでBitmapContextを作って描画する。)

という事になると思います。

だいたい目星がついたところで、(すごい適当だ!(笑))、サンプルのソースを見てみる事にします。

ドキュメント

で、まずはドキュメント。
Quartz 2D Programming Guide
http://developer.apple.com/documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/index.html
ざっと概要を読んでみると、

  • Quartz 2D ってのは、Mac OS Xがもつ描画エンジン。
  • Core Graphics frameworkの一部だそうで、PostScriptとPDFに基づいている。
  • カラーマネージメント(ColorSync)をサポート。フォトレンダリングApple Type Serviceを使ってるのだそうです。解るような解らないような(笑)
  • 要するにローレベルなAPIで、軽い。

なんて事が書いてあります。

自分のやりたい事は、サーバー上でPDFをJPEGに変換したいという、別の意味でローレベルな要求(笑)なのですが、CMYK画像の色がちゃんと出せたり、フォントがきれいにレンダリングできるのならば、それは願ったり叶ったりです。

なお、開発はPythonのラッパーがありますので、それをありがたく使わせていただきます。
サンプルのスクリプトはDeveloper's Toolsをインストールすると/Developer/Examples/Quartz/Pythonに入っていますので、これらを参考にします。うーん、お気軽だ。

Python Bindings for Quartz 2D 〜導入〜

たまにはMacネタでも。

Mac OS XTigerになって、PDFのサポートがかなり良くなりました。

サポートするPDFのバージョンは1.4という事で、これなら印刷屋的にも良い感じであります。

よくサーバー上でPDFをJPEGなんかに変換したい!なんて事はよくある(?)話だと思います。Mac OS Xを使っているならば、こいつのAPIをそっと叩いてJPEG変換してみるという手もあります・・というお話です。

そもそも、PDFをJPEGなどのビットマップに展開するユーティリティーなんて沢山ありますけど、日本語を扱う場合にいつもフォントの問題がつきまといます。GhostScriptなんかだとかなりいい感じで変換できますが、思うようにいかない事もしばしば・・。

印刷屋的に、「フォント」問題は「死活」問題なので、結構苦労します。PDFなんだから埋め込みゃいいじゃん!という話もありますが、それはそれでファイルサイズが大きくなったり、そもそも埋め込めないフォントがあったりと苦労も耐えません(涙)

そんなこんなで、ここ2年くらいの悩みの種だったんですが、OS XTigerになり、PDFのサポートがかなり使えるようになってきました。しかも、このAPIPythonからアクセスできると言うおまけ付き!!という事もあり、サーバで使える変換プログラムを作ってみようかなぁ・・という実験です。

プライマリキーの扱い

Cayenneのプライマリキーの管理は、基本的に「おまかせ」です。ほかのO/Rマッパーはよく知りませんが、EOFも同様です。
たとえば新しくデータをインサートする時などはプライマリキーの生成もCayenneが行ってくれます。そのための特別なテーブル(AUTO_PK_SUPPORT)が作られる場合もあります。(EOFではAUTO_PK_TABLEでしたっけ?)

CREATE TABLE AUTO_PK_SUPPORT (  TABLE_NAME CHAR(100) NOT NULL,  NEXT_ID INTEGER NOT NULL);

例えばEmployeeというテーブルを用意すると

CREATE TABLE Employee (empno INTEGER NOT NULL, ename VARCHAR(16) , sal FLOAT, PRIMARY KEY (empno));


コレに対応するEmployeeクラスは
Employee.java

public class Employee extends DataObject {
	public void setEname(String ename) {...}
	public String getEname() {...}
	public void setSal(Float sal) {...}
	public Float getSal() {...}
}


のような感じになります(ソースは適当です)。つまり、プライマリキーはユーザー側ではいっさい見ません。(empnoのゲッター/セッターがありません)

新しくEmployeeを追加する場合は、AUTO_PK_SUPPORTテーブルに以下のようなSQLが自動的に発行されて、新しいプライマリキーを発行します。

SELECT NEXT_ID FROM AUTO_PK_TABLE WHERE TABLE_NAME = 'Employee';

NEXT_IDをインクリメントして、

UPDATE AUTO_PK_TABLE SET NEXT_ID = NEXT_ID + 20 WHERE TABLE_NAME = 'Employee';

ただし、使用するデータベースによっては、AUTO_PK_TABLEを使用せずに、データベース自身が持つシーケンスなんかをつかう場合もあるようです。詳しくはドキュメントの4.7.3 Generated Primary Keyを参照してください。


もちろん、

public void setEmpno(Integer empno);
public Integer getEmpno();

をEmployeeオブジェクトに定義しても構いませんし、問題なく動作します。この場合はAUTO_PK_SUPPORTにUPDATEをかけるような動作はしません。プライマリキー管理はユーザ側で責任持ってねという事になります。
ちなみにプライマリキーを参照するメソッドが定義されていない場合でも、DataObjectUtilクラスでプライマリキーの値を取ってくる事は可能です。(EOFのEOUtilities.primaryKeyForObject()に相当。)


おそらくCayenneの住人(って、日本にいるんだろうか・・・?)、EOF界隈の人たちはプライマリキーについてあまり意識した事無いんじゃないかなあ?(一部から反発食らいそうですが(笑))
もちろん、プライマリキー自体に「意味がある」ってのはかなり多いと思いますんで、その辺の事はドキュメント 4.7.1 Meaningful Primary Key にも言及されています。


で、結局の所、キーの管理はCayenneにお任せでも、自分で管理しても、どちらでも構わないんですが、S2Cayenneでちょっと困った事がありまして・・

このプライマリキーの自動生成ですが、AUTO_PK_SUPPORTにアクセスする際、否応無しにInternalTransactionを使うんですよー。こりゃ参ったなー。Cayenneのバグなのかも??ちょっと調査してますよー。

Cayenneのトランザクション

現在、Cayenneトランザクションについて、いろいろと調べています。
CayenneはもともとJTA準拠なんてぜんぜん考えていなくて、トランザクション制御はCayenne自身が行っていました・・っていうか、今でもそうです。ただし、現状ではトランザクション制御はコンテナなど「外部にお任せする」というオプションがつきました。

DataDomain.setUsingExternalTransactions (boolean flag) 

もしくはCayenneModelerでContainer-Managed Transactionsにチェックを入れます。
直接モデルファイルに

<property name="cayenne.DataDomain.usingExternalTransactions" value="true"/>

を挿入してもOKで、いずれもやっている事は同じ事です。

Cayenneにはorg.objectstyle.cayenne.access.Transactionというそのものずばりのクラスがあります。これは抽象クラスで、実際はトランザクション制御を外部に委託する場合にはTransactionのサブクラスであるExternalTransactionが使用され、Cayenneにおまかせする場合はExternalTransactionクラスのサブクラス(Transactionのサブクラスじゃないよ。ややこしいけど)であるInternalTransactionクラスが使用されるようです。
ちなみにExternalTransaction、InternalTransactionはTransactionのインナークラスになってまして、JavaDocには出てきません(涙)。
また、名前はTransactionですが、JTAとは全く関係ありません。



DataContextがデータベースにアクセスするに、DataDomain.createTransaction()メソッドが呼ばれるようになっています。そこでDataDomainにセットされたUsingExternalTransactions属性によりExternalTransactionかInternalTransactionのインスタンスが返ってくるという仕掛けのようです。

で、S2Cayenneの場合、トランザクション制御はSeasarにやらせたい訳で、ExternalTransactionを使用する事になるんですが、こいつは何をするクラスかと言うと・・単純にcommit()やrollback()時にconnectionをcloseするというものです。(内部的にステータスの管理やTransactionDelegeteの通知なども行っているようです。)
トランザクション制御は外部が行うので、自分は基本的には何もしない・・という訳です。考えてみたらそりゃそうなんですけど、JTAのTransactionとCayenneのTransactionが両方登場して、軽くパニックになります・・

と言う訳で、やるべき事はSeasarが与えてくれるDataSourceをCayenneに与えば良い事になります。たぶん。

SeasarのDataSourceをCayenneに与える方法としてもっともスマートそうなのが、org.objectstyle.cayenne.conf.DataSourceFactoryを実装してあげて、これをConfiguration.setDataSourceFactory()してあげます。

これで、Cayenneに外部トランザクションを効かせるための準備は整った・・のかな?

うーむ。

うぅ・・WRさん経由(http://www.csus4.net/d/2005/07/18/s2cayenne/)でS2Cayenneの話が舞い込んできましたー。

id:koichikさんから直にトラックバックをいただき、ちょっとビビる(笑)。

どうしたものかなぁ・・まずは、ちゃんとSeasarを理解しなきゃだし、id:aua4ioさんの悩み(http://d.hatena.ne.jp/aua4io/20050701/1120205958)も今はあまり理解できてませんし・・(すんません)

でも、Seasarのプロジェクトに微力でも関われるなら非常に喜ばしい事なので、ぼちぼち頑張ってみようかなー?と思います。