ここでは、自己完結型の IBM FileNet P8 アプリケーションの一例を簡単に取り上げます。P8 はエンタープライズ・コンテンツ管理 (ECM) のための IBM プラットフォームです。実際の P8 プログラムは、J2EE や .Net などの大きなフレームワークの中に組み込むのが普通ですが、開発者としての出発点はやはりスタンドアロンのプログラムになると思います。スタンドアロンのプログラムを使用すれば、大きなフレームワークの複雑な処理に煩わされずに P8 の詳細だけに注意を集中できます。
HelloDocument というサンプル・アプリケーションは、さまざまなタスクを実行します。すべてのタスクを理解する必要はありませんが、ここでは基本的な内容だけを押さえておきましょう。このコードの基本的なテクニックを応用すれば、それぞれのユース・ケースに応じた拡張機能を追加できるようになります。さらに、HelloDocument を実行できれば、使用しているシステム環境の設定に問題がないことを確認できたことにもなるので、その後は環境に不安を抱くことなく、カスタム・アプリケーションの詳細だけに注意を集中できると思います。
この記事では Java のソース・コードを説明していくので、内容を理解するには Java に関するある程度の知識が必要になります。といっても、HelloDocument.java の各行を 1 つ 1 つ取り上げるわけではなく、Content Engine (CE) の API のポイントに関連する行を重点的に見ていきます。このコードには大量のコメントが含まれているので、それが煩わしいと思われるようであれば、この記事をスキップしてソース・コード自体をご覧いただくことも可能です (『ダウンロード』の項を参照してください)。
HelloDocument は、400 行から 500 行のコードを 1 つの Java ソース・ファイルにまとめた構造になっています。ソース・コードに含まれている大量のコメントを差し引いても、入門用のコードとしてはかなりの数の行になります。実際には別のクラスやソース・ファイルに分割したほうがよいと思われるような行も HelloDocument.java には含まれていますが、あえてすべてのコードを 1 つのファイルにまとめたのは、全体の流れを見渡しやすくするためです。例えば、HelloDocument.java ソース・ファイルには、Java Authentication and Authorization Service (JAAS) のログイン・シーケンス全体を含めており、コールバックを処理する内部クラスまで組み込んでいますが、実際のアプリケーションでは、そういうコードの書き方はしないはずです。HelloDocument クラスでは、PrivilegedAction インターフェースを実装しており、明示的な JAAS ログイン・モデルを簡単に見渡せるようにビジネス・ロジックの大半を run メソッドの内部にカプセル化しています。HelloDocument のビジネス・ロジックは 50 行から 100 行以内になっています。
この記事の執筆時点の現行リリースは P8 4.0.1 であり、P8 4.5.0 の開発サイクルが終わりに近づいている時期でした。このコードはどちらのリリースでも実行できます。(この記事とこのシリーズの続編では、P8 3.x の API を使用した作業にあまり時間をかけていません。それらの API は大きく異なっている部分もあります)。API で使用するクラスとメソッドは API の中核であり、大きな変更はありません。HelloDocument は、P8 の今後のいくつかのリリース・サイクルでも変更なしで実行できると思います。HelloDocument は Java で記述されており、P8 Content Java API と P8 Content .Net API の主な違いは命名規則と他の副次的な考慮事項だけなので、HelloDocument を C# などの .Net 言語で書き直す作業は、基本的には単純な置き換えだけで済むはずです。(1 つの例外として、その 2 つの API では認証の分野に違いがあります。この記事では HelloDocument の Java 認証についても触れますが、認証方式の概要を取り上げることはこの記事の守備範囲ではありません。)
HelloDocument が実際にどのような処理を行うかというと、文書を作成し、ファイルから文書のコンテンツをアップロードし、文書をチェックインし、フォルダーにファイルとして格納する、というのが基本的な流れです。さらに、文書を再び読み込んで、ダウンロードしたコンテンツと元のファイルのコンテンツを比較します。ダウンロードして比較する操作では、ファイルの一部をスキップするように設定することも可能です。文書の作成やファイルとしての格納は大抵のユース・ケースに共通する処理ですが、比較操作はそうではありません。実際には、アップロードしたコンテンツを検証する必要はありません。ダウンロード操作に関連するこのような余分の処理は、この API のコードの書き方を理解するための教材と考えてください。HelloDocument を実行する前に、CE でのセットアップ作業は特に必要ありません (ただし、新しいフォルダーや文書を作成するためのアクセス権の確認は必要です)。システム環境でこのアプリケーションを 1 回実行できたら、エラーなしで何回でも実行できます。
HelloDocument にはいくつかの構成項目があります。Content Engine (CE) の接続 URI (Uniform Resource Identifier) などは、ほとんどの P8 アプリケーションに共通する項目ですが、HelloDocument サンプルに固有の項目もあります。実際のアプリケーションでは、そのような構成項目をハードコーディングするよりも、コマンド行引数や構成ファイルなどの手段を利用して、アプリケーション・コード自体から構成項目を切り離すのが普通ですが、HelloDocument では便宜上、すべての構成項目を ConfigInfo という静的内部クラスの中で定数として定義しています (その一部を抜粋したのがリスト 1 です)。コードの中で ConfigInfo.SOME_VALUE への参照がある場合は、それらの定数への参照になります。
リスト 1. 静的内部クラス ConfigInfo
private static final class ConfigInfo
{
// . . .
/**
* This URI tells us how to find the CE and what protocol to use.
*/
static String CE_URI = "http://myCEserver:7001/wsi/FNCEWS40DIME";
/**
* This ObjectStore must already exist.
*/
static String OBJECT_STORE_NAME = "MyObjectStore";
// . . .
} |
ConfigInfo の各項目には使用法に関するコメントが付いています。各項目を確認し、それぞれの環境に適した値を設定してください。
この記事では、コードの書き方がテーマになっているので、Java クラスパスや他の外部構成項目の設定方法を詳しく取り上げることはしません。P8 プラットフォームの資料には、シック・クライアント環境のセットアップ方法に関する説明があります。その方法は、J2EE アプリケーション・サーバーのブランドごとに異なります。資料の同じ部分では、JAAS 設定の構成方法も取り上げられています。これから見ていくサンプルや操作手順は、それらの設定が正しく行われていることを前提にしています。
厳密にいえば、この API に含まれている Java オブジェクトは、CE リポジトリーに含まれているオブジェクトと同じではありません。この API に含まれている Java オブジェクトは、CE オブジェクトに対する参照であり、その参照によって、プロパティー値の確認、オブジェクト値プロパティー (OVP) によるナビゲート、各種の更新操作などが可能になります。API オブジェクトと CE オブジェクトの実際の比較は、API からサーバーへの往復処理が実行されるときに発生します。サーバーとの間の往復処理の数は、アプリケーションのパフォーマンスに大きな影響を及ぼすことが多いので、API では、往復処理の発生やそれぞれの往復処理でやり取りするデータの種類をきめ細かく制御できるようになっています。HelloDocument では、往復処理の数を最小限に抑えるためにコードを綿密に工夫していますが、要求または応答でやり取りするデータの量を最小限に抑えるための工夫はあまりしていません。ペイロードのサイズを調整するには、プロパティー・フィルターなどの手段を活用できます。そのようなプロパティー・フィルターについては、このシリーズの続編で詳しく取り上げる予定ですが、ここでは、プロパティー・フィルターを使用しなくてもこの API はそれなりに動作する設計になっていることを、とりあえず押さえておきましょう。この API の基本を理解できた時点で、プロパティー・フィルターの使用方法を学ぶ、というのが自然な流れです。プロパティー・フィルターを使用すれば、情報転送量の縮小といくつかの往復処理の結合という両面からパフォーマンスを大幅に改善できます。
この API には、Java オブジェクトをインスタンス化するときに便利な命名規則があります。ファクトリー・メソッド (および他のいくつかの種類のメソッド) の名前では、ローカル操作であることを示す接頭部として get を、CE サーバーとの間で往復処理を実行することを示す接頭部として fetch をそれぞれ使用します。(Java オブジェクトをインスタンス化するだけでなく、リポジトリーなどで新しい CE オブジェクトを作成する場合は、3つ目の動詞として、create を使用します。)サーバーとの間で往復処理を実行しないで Java オブジェクトをインスタンス化することを、API の用語では、フェッチなしのインスタンス化ともいいます。例えば、conn = Factory.Connection.getConnection(ConfigInfo.CE_URI) はローカル操作になります。HelloDocument のソース・コードでは、フェッチなしのインスタンス化や、往復処理が必要だと勘違いしてしまいがちな他の操作を明示するために、「no R/T」(No Round-Trip:往復処理なし) というコメントを随所に入れています。さらに、可能な限り get メソッドを使用している点も押さえておいてください。
フェッチなしのインスタンス化では、面白いテクニックを使用できます。つまり、参照先の CE オブジェクトが実際に存在するという前提で処理を進める API をだますようなコードの書き方が可能だということです。ただし、そのようなコードにはコストもかかります。そのオブジェクトを対象にした往復処理が発生する時点で、結局余計な処理が必要になるからです。その時点で、CE サーバーはオブジェクト参照と実際の CE オブジェクトを綿密に比較して、調整を図ります。もちろん、CE オブジェクトの作成は、所定の方法で処理する特殊ケースになります。実際には存在しない CE オブジェクトを参照するのが効率的な場合はほかにもありますが、そのようなケースを取り上げるのはこの記事の守備範囲ではありません。いずれにしても、CE オブジェクトは、CE が CE オブジェクトを対象にした要求を受け取る時点で存在していればよいわけです。
この部分の説明をまとめるとこうなります。つまり、フェッチなしのインスタンス化を使用するとパフォーマンスを改善できますが、存在しないオブジェクトの問題を後からアプリケーションのエラー処理によって解決しなければならないというコストも発生します。ただし、そのようなエラー処理のコストはそれほど大きくありません。
HelloDocument.run メソッドには、主たる包括的なビジネス・ロジックを組み込んでいます。そのメソッドの冒頭と Connection のインスタンス変数の部分は、一般的なコーディング・パターンの典型的な例です (リスト 2 を参照してください)。ほとんどのコンテンツ・アプリケーションは CE リポジトリーの中にあるオブジェクトだけを処理し、しかも 1 つのリポジトリーだけを対象にしています。
リスト 2. HelloDocument.run とその前提コード
/**
* All interaction with the server will make use of this Connection object.
* Connections are actually stateless, so you don't have to worry about
* holding open some CE resource.
*
* no R/T
*/
private Connection conn = Factory.Connection.getConnection(ConfigInfo.CE_URI);
// ...
/**
* This method contains the actual business logic. Authentication has
* already happened by the time we get here.
*/
public Object run()
{
// Standard Connection -> Domain -> ObjectStore
//no R/T
Domain dom = Factory.Domain.getInstance(conn, null);
//no R/T
ObjectStore os = Factory.ObjectStore.getInstance(dom,
ConfigInfo.OBJECT_STORE_NAME);
String containmentName = createAndFileDocument(dom, os);
File f = new File(ConfigInfo.LOCAL_FILE_NAME);
long fileSize = f.length();
System.out.println("Local content size is " + fileSize + " for file "
+ ConfigInfo.LOCAL_FILE_NAME);
long skipPoint = 0L;
if (ConfigInfo.USE_SKIP)
{
long midPoint = fileSize / 2;
// pick a random point in the second half of the content
skipPoint = midPoint + (long)Math.floor((Math.random() * midPoint));
}
System.out.println("Will skip to " + skipPoint + " of " + fileSize);
readAndCompareContent(os, containmentName, skipPoint);
return null;
}
|
ObjectStore に接続するためのコーディング・パターンは以下のとおりです。
- Connection オブジェクトの取得
- Domain オブジェクトの取得
- ObjectStore オブジェクトの取得
Connection は軽量のクラスです。API は、CE サーバーへの接続方法をこのクラスから判別します。API と CE サーバーの対話は、CE サーバーの側から見るとステートレスであるため、Connection オブジェクトは、高コストのサーバー・サイド・リソースを開いたままにしておきません。Connection の中に格納する主な情報項目は、使用する URI です。API は接続方法と CE の場所をその URI から引き出します。特に押さえておきたいポイントは、Connection オブジェクトにユーザー情報を格納していないことです。Connection では追加の構成パラメーターを設定することもできますが、その方法についてはここでは取り上げません。
Domain は、ObjectStore または、その上位層で P8 リソースを格納するためのオブジェクトです。Java の Domain オブジェクトをインスタンス化するには、Connection とドメイン名が必要です。現時点で P8 のインストール・システムには 1 つのドメインしかないので、この API では、ヌルのドメイン名をファクトリー・メソッドに渡してもよいことになっています。ドメイン名は P8 のインストール時に設定されます。ドメイン名をどうしても確認したい場合は、ドメイン名のプロパティー値を調べることができます。
ObjectStore オブジェクトは、CE リポジトリーに対応しています。ObjectStore オブジェクトを作成するためのファクトリー・メソッドでは、Connection オブジェクトではなく、Domain オブジェクトを引数として使用します。(要するに、ObjectStore の対象スコープは Domain になっているわけですが、この記事ではスコープについて詳しく取り上げることはしません。)API の内部では、Domain と、Domain からインスタンス化する各種オブジェクトのために、同じ Connection オブジェクトを使用します。後で ObjectStore 自体を使用して各種オブジェクトをインスタンス化するときには、API の内部で Connection が自動的に渡されることになります。
このあたりのコードは、ファクトリー・メソッドの使用法を示す典型的な例でもあります。CE API には多数のファクトリー・クラスがあり、1 つの CE クラスにつき大体 1 つのファクトリー・クラスが存在することになります。便宜上、それらのファクトリー・クラスは、com.filenet.api.core.Factory クラスの中にネスト構造で編成されています。特定の CE クラスに対応するファクトリー・クラスを確認するには、対象の CE 内部クラス (Document や Folder など) の Factory の中を調べてみてください。その内部クラスの中には、対象の CE クラスに対応する Java オブジェクトをインスタンス化するための少数のメソッドだけが含まれています。それらのファクトリー・メソッドで使用するパラメーターを判別するのは容易です。ファクトリー・メソッドは、特定のタイプを返すので、タイプ・セーフなメソッドといえます。例えば、Factory.Folder の各種メソッドは、それぞれ Folder タイプのオブジェクトを返します。一般に、ファクトリー・メソッドの背後にあるタイプ・セーフという考え方の目指すところは、プログラミング・エラーを減らすということです。キャストを使用する必要はないので、コンパイラーが問題を検出する可能性は高くなります。さらに、実行時のタイプ・チェックでも有利になります。
一方、タイプ・セーフではないメソッド群もあります (メソッド数はそれほど多くなく、HelloDocument でもあまり使用していません)。API の用語では、汎用メソッドともいいます。ObjectStore.getObject はその一例であり、ほとんどすべての独立した CE クラスのオブジェクトを取得できます。汎用メソッドは基本的に、さまざまなタイプを汎用的に処理しなければならないアプリケーション・コーディング・パターンで使用します。あらかじめタイプがわかっているオブジェクトを処理する場合であれば、汎用メソッドを使用してもあまり意味はありません。
HelloDocument.createAndFileDocument メソッドの最初の数行では、コンテンツに基づいて新しい文書を作成するための準備作業を行います (リスト 3 を参照してください)。それらのコード行の末尾の段階では、Java の Document インスタンスに対して実行するほとんどの操作の準備が整っていますが、CE の Document インスタンスは作成されていない状態です (サーバーとの間で必要な往復処理をまだ実行していないからです)。
リスト 3. HelloDocument.createAndFileDocument の内部 (パート 1)
//no R/T
ContentTransfer ct = Factory.ContentTransfer.createInstance();
ct.setCaptureSource(fis);
// optional
ct.set_RetrievalName(ConfigInfo.LOCAL_FILE_NAME);
// optional
ct.set_ContentType("application/octet-stream");
ContentElementList cel = Factory.ContentElement.createList();
cel.add(ct);
//no R/T
Document doc = Factory.Document.createInstance(os, null);
//not required
doc.getProperties().putValue("DocumentTitle", ConfigInfo.DOCUMENT_TITLE);
doc.set_ContentElements(cel);
//no R/T
doc.checkin(AutoClassify.DO_NOT_AUTO_CLASSIFY, CheckinType.MAJOR_VERSION);
|
CE のオブジェクト・モデルを理解していないと、リスト 3 の操作の流れは若干わかりにくいかもしれません。特に問題になるのは、Document とそのコンテンツの関係です。スプレッドシートやテキスト文書などのコンテンツは、コンテンツ・エレメントと呼ばれるオブジェクトの中に格納されています。そのコンテンツは Document と一緒にリポジトリーの中に格納されているので、このコードでは ContentTransfer というコンテンツ・エレメント・サブクラスを使用しています。(もう 1 つのサブクラスである ContentReference は、実際のビットが他の場所に格納されている場合に使用します。)1 つの Document に対して任意の数のコンテンツ・エレメントを使用できます。コンテンツ・エレメントは、CE の用語でいう「従属オブジェクト」なので (つまり、単独では存在し得ず、格納されている Document が必要なので)、Document の ContentElements プロパティーのタイプは ContentElementList になっています。
これで、少し流れをつかみやすくなったと思います。つまり、ファクトリー・メソッドを使用して、ContentTransfer オブジェクト、ContentElementList オブジェクト、および Document オブジェクトを作成し、ContentTransfer オブジェクトを ContentElementList オブジェクトに追加し、ContentElementList から Document の ContentElements プロパティーの値を設定する、という流れです。もちろん、このあたりの順序は柔軟に考えてもよいので、よりスムーズに思える流れがあれば、その流れに沿って各行を並べ替えても構いません。
このコードは、タイプ・セーフなプロパティー・アクセス用メソッドの使用法を示す典型的な例でもあります。例えば、コンテンツ・エレメントの取得名 (後でコンテンツをダウンロードするアプリケーションにとってファイル名を判別するためのヒントになるオプション・プロパティー) を設定するときには、ct.set_RetrievalName(ConfigInfo.LOCAL_FILE_NAME) を呼び出します。そのメソッドで有効なのはストリングの引数だけなので、整数値を使用した場合はコンパイル時エラーになります。それに対応する取得用メソッド ContentTransfer.get_RetrievalName もタイプ・セーフであり、ストリング値を返します。set_ と get_ (下線付き) に関する Java API の命名規則を理解していれば、単に標準的な Java オブジェクト・フィールドではなく CE リポジトリーのプロパティーを処理しているという意味合いを確認できます。あらゆる CE クラスのあらゆるシステム定義プロパティーには、タイプ・セーフなアクセス用メソッドがあります。本来読み取り専用のプロパティーの場合は、タイプ・セーフな設定用メソッドはありません。
DocumentTitle プロパティーの設定 (doc.getProperties().putValue("DocumentTitle", ConfigInfo.DOCUMENT_TITLE) という行) は、他の設定用メソッドの呼び出しとはかなり違っています。どうしてそのようになっているのでしょうか。意外に思えるかもしれませんが、DocumentTitle はシステム定義プロパティーではありません (CE サーバーの観点からすれば、システム定義プロパティーは、CE サーバーが値を作成するためのプロパティーあるいは CE サーバーの動作に影響を与えるプロパティーのいずれか、または両方の意味合いを持ちます)。DocumentTitle プロパティーは、ObjectStore の作成時に AddOn によって定義されるので、DocumentTitle プロパティーのない ObjectStore はほとんど見たことがないと思いますが、現にシステム定義プロパティーではないので、タイプ・セーフなアクセス用メソッドは API の中に用意されていません。したがって、汎用メソッドを使用して値を設定しなければならない、ということになります。
Document.getProperties メソッドからは、ローカル・キャッシュに入っているその Document のプロパティー値に対応する Properties オブジェクトが返され、Properties クラスには、プロパティー値の取得や設定に使用できる汎用メソッドが含まれています。ただし、実際のアプリケーションでは、プロパティー値の汎用アクセスに関するモデルを理解できるように、カスタム・プロパティーを処理することになると思います。
既に見たとおり、checkin メソッドの呼び出しの後でも、CE リポジトリーの中では Document がまだ作成されていません。むしろ、1 つ以上の保留操作が存在します。保留操作とは、サーバーで実行しなければならない操作をオブジェクトに関連付ける内部のタグ付けです。(この API には、保留になっている操作を確認するための機能が用意されていますが、実行する必要はまずないと思います。)例えば、Java の Document オブジェクトを作成するためのファクトリー・メソッドを呼び出した場合は、そのオブジェクトに Create の保留操作を関連付けるタグが自動的に生成され、checkin メソッドを呼び出した場合は、Checkin の保留操作が追加される、といった具合です。この API のすべてのクラスで保留操作が 18 種類しかないのは意外に思えるかもしれませんが、CE の多くの操作は、実際にはオブジェクトやプロパティーに対する通常の CRUD 操作 (Create、Retrieve、Update、Delete、つまり、作成、取得、更新、削除) で成り立っていることを考えれば、そのように種類が少ないのもうなずけます。例えば、プロパティー値の更新は、対象のプロパティーやクラスの種類にかかわりなく、Update の保留操作によって、または他の種類の保留操作の補助機能として常に実行されます。保留操作は CE サーバーに対する命令と考えれば、わかりやすいかもしれません。つまり、CE サーバーは、オブジェクトがサーバー側に到着した時点で、その命令に基づいてそのオブジェクトに何かの操作を実行するわけです。
では、その checkin メソッドの呼び出し doc.checkin(AutoClassify.DO_NOT_AUTO_CLASSIFY, CheckinType.MAJOR_VERSION) を詳しく見てみましょう。このメソッドには、別々の定数クラスで定義されている 2 つの引数があります。パラメーター値のためのこれらの定数クラスには、タイプ・セーフな列挙パターンを記述しています。この API は元々、Java 1.4 環境のために作成されており、今でも Java 1.4 環境に対応しているので、Java 言語の列挙型は使用できません。このタイプ・セーフな列挙パターンでは、適切な選択項目のリストにある定数を強制的に使用させるという形でタイプ・セーフな動作を実現しています。例えば、checkin メソッドの 2 つの引数の順序が逆になってしまえば、Java コンパイラーによってそのエラーが検出されます。つまり、実行時エラーではなく、コンパイル時エラーになります。そのような形でタイプ・セーフな動作を実現しなければ、エラーに気付かずに不正な動作を発生させてしまう可能性があります。
後で Folder のインスタンス化に話を戻しますが、まずはその Folder に Document をファイルとして格納する操作を見ておきましょう。この API には、Folder.file、Folder.unfile というメソッドがあります。意外なことに、それらのメソッドは実際にはヘルパー・メソッドであり、API の基本メソッドではありません。確かに文書をファイルとしてフォルダーに格納するという考え方は CE の基本的な特色ではありますが、ファイルとしての格納は、実際にはFolderとその中に組み込む文書をリンクする関係オブジェクトの作成という形で行います。ですから、ここで少し立ち止まって、Folder.file メソッドを呼び出さずに文書をファイルとして格納する方法を見ておくのも悪くはありません (リスト 4 を参照してください)。
リスト 4. HelloDocument.createAndFileDocument の内部 (パート 2)
Folder folder = instantiateFolder(os);
//no R/T
DynamicReferentialContainmentRelationship rcr =
Factory.DynamicReferentialContainmentRelationship.createInstance(os, null,
AutoUniqueName.AUTO_UNIQUE,
DefineSecurityParentage.DO_NOT_DEFINE_SECURITY_PARENTAGE);
rcr.set_Tail(folder);
rcr.set_Head(doc);
rcr.set_ContainmentName(ConfigInfo.CONTAINMENT_NAME);
|
DynamicReferentialContainmentRelationship (DRCR) というクラスは、名前は長いですが、作成方法は簡単です。このクラスでは、Folderとその中に格納する文書をリンクします。その関係をFolderから文書に向かう矢印として図式化してみれば、Folderにリンクする側の OVP が Tail と呼ばれ、文書にリンクする側の OVP が Head と呼ばれるのもうなずけます。
DRCR は動的なクラスです。ターゲットの Document のバージョンが更新されると、Head プロパティーも自動的に更新されます。DRCR をインスタンス化するには、該当するファクトリー・メソッドを呼び出します。そのメソッドによって、Create の保留操作付きの Java インスタンスが作成され、いくつかのシステム・プロパティーが設定されます。この場合は、AutoUniqueName.AUTO_UNIQUE を使用しているので、包含関係の名前が重複していれば、CE サーバーによってその名前が変更されます (包含関係の名前は Folder ごとに固有でなければなりません)。あるいは、名前が重複している場合に例外で操作が失敗するようにコードを記述する、という方法もあります。
DRCR はサブクラス化が可能なので、サブクラスを作成して独自のメタデータ・プロパティーを追加すると便利だと思う時があるかもしれません。いずれにしても、CE リポジトリーの中では、DRCR もターゲットの Document もまだ作成されていません。
では、いよいよ CE リポジトリーへの書き込み操作に移りましょう。もちろん、Document と DRCR に対して save メソッドをそれぞれ呼び出せば事は足りるわけですが、ここでは往復処理の最小化というテーマに沿って、UpdatingBatch でその両方を保存することにします (リスト 5 を参照してください)。
リスト 5. HelloDocument.createAndFileDocument の内部 (パート 3)
Folder folder = instantiateFolder(os);
//no R/T
DynamicReferentialContainmentRelationship rcr =
Factory.DynamicReferentialContainmentRelationship.createInstance(os, null,
AutoUniqueName.AUTO_UNIQUE,
DefineSecurityParentage.DO_NOT_DEFINE_SECURITY_PARENTAGE);
rcr.set_Tail(folder);
rcr.set_Head(doc);
rcr.set_ContainmentName(ConfigInfo.CONTAINMENT_NAME);
|
UpdatingBatch の考え方は非常に単純で、例えてみれば、バケツに物を入れて目的地 (CE サーバー) まで運ぶというような発想です。つまり、UpdatingBatch インスタンスを作成し、そのインスタンスに各オブジェクトを追加し、updateBatch メソッドを呼び出してすべてをサーバーに送信するわけです。もちろん、追加するのは、CE リポジトリーに保存しなければならない変更内容があるオブジェクトということになります。UpdatingBatch には、往復処理の最小化という目的のほかにもう 1 つの重要な目的があります。つまり、UpdatingBatch に含まれているすべてのオブジェクトは、サーバーでアトミックなトランザクションとして処理されます。要するに、すべて成功するか、すべて失敗するか、という処理です。HelloDocument ではそのような処理は必要ありませんが、トランザクションの動作が必要なユース・ケースは実際に珍しくありません。UpdatingBatch は、リーズナブルなパフォーマンス・コストでそのような動作を実現するための優れた方法です。
この例では、UpdatingBatch の作成時に、サーバーとの間の往復処理によって更新後のオブジェクト・インスタンスを返すための引数を UpdatingBatch に指定しています。ほとんどの場合は、オブジェクトを更新する必要はありませんし、更新するとしても、プロパティー・フィルターで更新のサイズを制限するのが普通ですが、この場合は、包含関係の名前に関する動作を確認するためにあえてその引数を追加しました。既に見たとおり、このコードでは、包含関係の固有の名前をサーバーが自動的に選択するようになっているので、DRCR で指定した名前とは異なる名前をサーバーが選択する可能性もあります。更新後のプロパティー値を見れば、CE サーバーが実際に使用した名前を確認できるというわけです。
先ほどは Folder オブジェクトのインスタンス化のコードを飛ばしていましたが、ここでその話に戻りましょう。リスト 6 に HelloDocument.instatiateFolder のコードを示します。
リスト 6. HelloDocument.instantiateFolder
private Folder instantiateFolder(ObjectStore os)
{
Folder folder = null;
try
{
//no R/T
folder = Factory.Folder.createInstance(os, null);
//no R/T
Folder rootFolder = Factory.Folder.getInstance(os, null, "/");
folder.set_Parent(rootFolder);
folder.set_FolderName(ConfigInfo.FOLDER_NAME);
//R/T
folder.save(RefreshMode.NO_REFRESH);
}
catch (EngineRuntimeException ere)
{
// Create failed. See if it's because the folder exists.
ExceptionCode code = ere.getExceptionCode();
if (code != ExceptionCode.E_NOT_UNIQUE)
{
throw ere;
}
System.out.println("Folder already exists: /" + ConfigInfo.FOLDER_NAME);
//no R/T
folder = Factory.Folder.getInstance(os, null, "/" + ConfigInfo.FOLDER_NAME);
}
return folder;
}
|
instantiateFolder メソッドの実装は、少々不自然に見えるかもしれません。というのも、ここでは HelloDocument を事前のセットアップなしで何度でもスムーズに実行できるようにすることを目指したからです。そのために、まず フォルダーの作成を試み、その操作が失敗したときにフェッチなしのインスタンス化にフォールバックする、という流れでコードを書きました。もちろん、そのような流れは非効率的なので、実際のアプリケーションでそういうコードを書くことはまずないと思います。その逆に、フォルダーのフェッチを実行してから、その操作が失敗したときに作成操作にフォールバックする、という流れも考えられますが、やはりフォルダーの存在を確認するための往復処理のコストがかかります (ほとんどの場合は、フォルダーが既に存在していることをアプリケーションの側で把握しているはずです)。そう考えると、パフォーマンスの観点から見て最適な方法は、フォルダーがリポジトリーの中に存在するという前提でフェッチなしのインスタンス化を実行する、ということになります。ただし、フォルダーが実際に存在しなかった場合のエラー処理を後の方のコードで調整することが必要です。(ここでは、複雑なエラー処理コードでサンプルが煩雑にならないような方法を選択したというわけです。)
ここでは、リポジトリーからコンテンツを読み込む操作を深く掘り下げることはしません。ロジックのほとんどは、ごく普通の Java ストリーム処理だからです。リスト 7 を見ながらいくつかのポイントだけを押さえておきましょう。
リスト 7. HelloDocument.readAndCompareContent の内部
String fullPath = "/" + ConfigInfo.FOLDER_NAME + "/" + containmentName;
System.out.println("Document: " + fullPath);
//no R/T
Document doc = Factory.Document.getInstance(os, null, fullPath);
//R/T
doc.refresh(new String[] {PropertyNames.CONTENT_ELEMENTS});
InputStream str = doc.accessContentStream(0);
|
構成値と CE サーバーから返された実際の包含関係の名前に基づいて Document の絶対パスを組み立てているのは、そのようなことも可能だということを示したかったからに過ぎません。作成した Document の ID 値を refresh によって取得し、その ID によって Document をインスタンス化するコードも、同じように簡単に記述できます。(パスを使用するよりも ID を使用するほうがいくらか効率的です。CE サーバーが何かの方法でパスを ID に変換するにはコストがかかるからです。)
ここでは、ファクトリー・メソッドによって Document をフェッチなしでインスタンス化してから、refresh メソッドをすぐに呼び出して ContentElements プロパティーの値を取得します。この場合にそうしているのは、主に refresh 呼び出しの方法を示すためです。ファクトリー・メソッド fetchInstance によって Document をインスタンス化するコードでも、同じ程度のパフォーマンスが得られます。適切なプロパティー・フィルターを使用して、フェッチするデータの量を制限する場合は、特にそう言えます。さらに、Document.accessContentStream メソッドは、対象の ContentTransfer 依存オブジェクトに対して accessContentStream を呼び出すための便利な方法です。
ここでは、既に少し取り上げた HelloDocument の冒頭の部分に話を戻しましょう。もちろん、認証そのものについて詳しく取り上げることはしませんが、HelloDocument の認証コードの流れを押さえておきたいと思います。
CE モデルでは、認証を JAAS に完全に任せてしまいます。つまり、CE API または CE に実際にログオンすることはありません。むしろ、CE API では、JAAS のログイン・シーケンスが既に実行されているという前提で CE 呼び出しを進めていきます。したがって、CE API では、認証に使用する各種のメソッドをコーディングしなくてもよいというメリットがあります。JAAS はプラグ可能なアーキテクチャーになっているので、従来型のユーザー ID/パスワードの方式や指紋リーダー、携帯用の認証機器など、さまざまな技法を利用できます。どんな方法で認証を行うにしても、CE API のアプリケーション・コードは変わりません。
リスト 8 の冒頭に HelloDocument.main の認証関連コードがあります。後で loginAndRun メソッドの話に戻りますが、その前に else 文節のコードを少し見ておきましょう。
CE API の UserContext クラスには、認証に関連した便利なメソッドがいくつかあります。いずれも、従来型のユーザー ID/パスワードの方式に限られているレガシー・システムでの認証を簡略化するために用意されているメソッドです。UserContext.createSubject メソッドは、JAAS の Subject のインスタンスを作成するときに使用します。UserContext オブジェクトには、JAAS の Subject のスタックも格納できます。その中で最上位の Subject が CE との間の往復処理で実際に使用されます。UserContext.pushSubject メソッドと UserContext.popSubject メソッドには、標準的なスタック・パラダイムが実装されています。スタックそのものは、スレッド・ローカルな格納場所に格納されます。つまり、スタックは特定の実行スレッドに関連付けられている、ということです。特に押さえておきたい点として、popSubject の呼び出しは finally ブロックの中にあるので、else 文節でどんなことが起きても呼び出されるようになっています。この動作は非常に重要です。確かに、スタンドアロンの HelloDocument アプリケーションではそれほど大きな意味はありませんが、J2EE アプリケーションでは要求に対応するためにスレッド・プールを使用するのが普通であり、そのようなアプリケーションではその動作が非常に重要になります。さまざまな場所でコードを再利用する可能性がある以上、いつでもコードを正しく書くようにする習慣は大切です。さらに、CE の呼び出しが完了した後に、スレッドの認証コンテキストをクリーンアップすることもお勧めします。
リスト 8. HelloDocument で使用している認証用のメソッド
public static void main(String[] args) throws LoginException
{
System.out.println("CE is at " + ConfigInfo.CE_URI);
System.out.println("ObjectStore is " + ConfigInfo.OBJECT_STORE_NAME);
HelloDocument fd = new HelloDocument();
if (ConfigInfo.USE_EXPLICIT_JAAS_LOGIN)
{
loginAndRun(fd, ConfigInfo.USERID, ConfigInfo.PASSWORD);
}
else
{
// This is the standard Subject push/pop model for the helper methods.
Subject subject = UserContext.createSubject(fd.conn, ConfigInfo.USERID,
ConfigInfo.PASSWORD, ConfigInfo.JAAS_STANZA_NAME);
UserContext.get().pushSubject(subject);
try
{
fd.run();
}
finally
{
UserContext.get().popSubject();
}
}
}
|
では、UserContext.pushSubject によって、CE との間の往復処理で使用する Subject をアクティブ化する、というコードを記述しない場合は、どうなるのでしょうか。その場合は、CE API の内部処理が、システム環境に基づく JAAS Subject を探すことになります。システム環境に基づく Subject というのは、標準の JAAS メカニズムによってスレッドに関連付けられている Subject という意味です。例えば、Web アプリケーションの場合は、J2EE Web コンテナーにログインする、といった具合です。HelloDocument では、loginAndRun メソッドによる処理の一部としてログインを明示的に実行します。
リスト 9. loginAndRun
private static final void loginAndRun(HelloDocument fd, String userid,
String password) throws LoginException
{
LoginContext lc = new LoginContext(ConfigInfo.JAAS_STANZA_NAME,
new HelloDocument.CallbackHandler(userid, password));
lc.login();
Subject subject = lc.getSubject();
Subject.doAs(subject, fd);
}
|
リスト 9 からもわかるとおり、loginAndRun メソッドは非常にシンプルです。そのメソッドには標準の JAAS ログイン・シーケンスが実装されていますが、内部クラス HelloDocument.CallbackHandler の実装はリスト 9 に含まれていません。その実装も標準の JAAS の処理だからです。ただし、そのような簡略化の背後には、巧妙なごまかしがあります。リスト 9 の最終行にある Subject.doAs メソッドの呼び出しは、それぞれの J2EE アプリケーション・サーバーに固有の呼び出しになっています。そのあたりの詳細は、J2EE の仕様で十分に記述されていないので、各ベンダーがそれぞれ独自の方法で実装しなければなりません。将来的には、J2EE の仕様でその問題が解決され、ログインのための呼び出しでベンダー固有のロジックを使用しなくても済むようになると思います。いずれにしても、シック・クライアントではない本当の意味での J2EE クライアントであれば、J2EE コンテナーで認証を行い、そのコンテナーが詳細を処理する、という考え方になるのは間違いないのではないでしょうか。
この記事では、HelloDocument アプリケーションを詳しく見てきました。具体的には、ObjectStore に接続する方法、Folder や Document などの各種オブジェクトを作成する方法、プロパティー値の読み取りと設定の方法、コンテンツのアップロードとダウンロードの方法などです。もちろん、そのような機能を組み合わせても、シンプルなアプリケーションであることに変わりはありませんが、さらに高度なアプリケーションで使用できるコーディング・パターンという意味では十分ではないかと思います。
P8 コードの作成を始めたばかりの方であれば、HelloDocument を開始点としてご使用ください。完全版のソース・ファイルは、以下に示すリンクからダウンロードできます。ソース・ファイルを変更していろいろな動作を試してみてください。HelloDocument.java. に手を加えて簡単なアプリケーションを作成していただければ幸いです。
それでは、このシリーズの続編もお楽しみに。
| 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|---|---|---|
| この記事の Java ソース・コード | HelloDocument.zip | 6KB | HTTP |
学ぶために
- 「IBM FileNet Content Manager Implementation Best Practices and Recommendations (US)」と「IBM FileNet Business Process Manager 入門 (US)」をお読みください。CE と PE の API についてこの連載よりもさらに詳しく学習できます。この製品群に関する理解を深めるための貴重な資料です。
- filenet (US) を検索すると、関連する IBM Redbook がさらに見つかります。
- テクニカル・ライブラリー (US)の中を検索すると、IBM FileNet 製品に関する多彩な執筆陣によるさまざまな資料が見つかります。
- 「Develop applications using the IBM Enterprise Content Management Java APIs with IBM Rational Application Developer (US)」というチュートリアルでは、IBM FileNet Content Manager をはじめとするさまざまな IBM ECM 製品の API に関するコーディング技法を比較できます。
- 「Upgrade from FileNet P8 3.5 to 4.0: An architectural shift (US)」という記事では、J2EE を統合した P8 プラットフォームの主な特色を確認できます。P8 と J2EE のアーキテクチャーの統合を理解することは、カスタム・ソリューションの開発方法を理解する鍵となります。
- 「FileNetによるBPMアプリケーションの構築 【Build BPM applications using FileNet(US)】」という連載では、IBM FileNet Business Process Framework (BPF) を使用して一般的なソリューションを作成する方法を具体的に学べます。
製品や技術を入手するために
- エンタープライズ・コンテンツ管理のページには、IBM ECM 製品群に関する情報が満載です。「Enterprise Content Managementのページ(US) のLibrary」のセクションでは、ホワイト・ペーパー、製品概要、アナリスト・レポートなど、さまざまな製品情報を確認できます。
- ECM FN01 オンラインマニュアルでは、P8 プラットフォームの API や他の特色に関する資料を参照できます。
議論するために
- developerWorks には ECM (US) 関連のさまざまなフォーラムがあり、IBM FileNet 製品に特化したサブフォーラムもあります。特に、IBM FileNet Content Manager (US) フォーラム と FileNet Business Process Manager (US) フォーラムを確認してください。これらのフォーラムは主にコミュニティー主導型であり、公式の製品サポート機構の代わりとなるものではありません。

Bill Carpenter は、ワシントン地区にあるシアトル IBM の ECM アーキテクトです。IBM のエンタープライズ・コンテンツ管理事業に、開発者、開発マネージャー、アーキテクトとして、これまで 10 年間かかわってきました。彼は、資料「IBM FileNet Content Manager Implementation Best Practices and Recommendations」の共著者です。以前、フォーチュン 50 の複数の企業で大規模ソフトウェア・システムを構築した経験があり、インターネット・ベンチャー企業の CTO を務めたこともあります。いくつかのオープン・ソース・プロジェクトにおいて、メーリング・リストとパッチの分野で頻繁に貢献しています。Bill は、ニューヨーク州トロイの Rensselaer Polytechnic Institute で数学とコンピューター・サイエンスの学位を取得しています。