P8 API は、エンタープライズ・アプリケーションの開発で主に使用されます。エンタープライズ・アプリケーションの開発においては、いずれパフォーマンスが問題視されることになります。独立した開発環境においてパフォーマンスが良好でも、多数のユーザーが利用する現実的な負荷の下では、開発したアプリケーションがどのようなパフォーマンスで動作するかを予測するのは困難です。将来の記事では、最高のパフォーマンスを引き出すための特定のプログラミング手法を解説します。今回の記事では、その下準備として、渡しているデータを確認するためのツールと手法について説明します。プログラミングの要点からは少し逸脱しているように見えるかもしれませんが、このシリーズの記事を読み進め、独自のアプリケーションを開発するときは、この情報が有益なものとなります。
多くの優れたアプリケーションでは、主に以下の 2 つの点でパフォーマンスの向上を図っています。
- 最初に、全体的なパフォーマンス状況は、多くの場合、クライアント・アプリケーションが P8 サーバーに対して行うネットワーク呼び出しを何回行っているかに左右されます。誰に聞くかにもよりますが、ネットワーク呼び出しには、多数の呼び方があります。例えば、ラウンド・トリップ (R/T) やリモート・プロシージャー・コール (RPC)、サーバー・ヒットなどがあり、その他にも、似たような名前があります。これらの名前はすべて同じことを意味し、クライアントからサーバーに要求を送り、サーバー側で計算し、その応答を返すために必要な『配管』のようなものを意味しています。開発環境では、RPC は、数十~数百ミリ秒しかかからないかもしれません。実際、独立した環境では、P8 RPC の処理時間が長くかかることは比較的まれなことです。十分な負荷をかけた環境では、ネットワーク、サーバー、また、場合によってはクライアント上で処理要求が競合すると、この時間が長くなります。さまざまな負荷が高い環境では、できるだけ多くのオーバーヘッドを排除または削減することで、よい結果が得られます。P8 サーバーで実行する必要のある 2 つの処理において、これらを 2 回の RPC呼び出しではなく、1 回の RPC呼び出しで処理できれば、必ずコストが削減されます (非常に異常なケースは除きます)。
- 次に、パフォーマンスは、RPC で実際にネットワーク上を移動するデータ量 (バイト数) の影響を受けます。これは、ネットワーク・ルーターの単なるビット処理速度の問題ではありません。関連するデータが増えると、クライアント API およびその対応するサーバーが複数の要求を組み合わせて解釈することがより複雑になります。通常、P8 環境では、要求のサイズは、応答のサイズと比べてごくわずかです。そのため、要求サイズの調整ができないということはほとんどありません。多くの場合、RPC 応答のサイズを調整すると、パフォーマンスに大きな影響が及びます。まずは、その調査を行うことが重要になります。
この記事では、開発したアプリケーションが上記2つの観点からどのように動作しているかを理解するための説明をしていきます。
- ネットワーク・トレース・ツールは、ネットワーク上の実データの流れを詳細に調査するのに役立つツールです。このツールは難しいものと考えられがちですが、最近のこういったツールは、かなり操作しやすくなっており、直感的に掘り下げて調べることができます。
- P8 Content Java API ロギングでは、さまざまな視点でAPI が何をしているのかを知ることができます。これは、RPC 呼び出し数とサイズの両方を評価するための便利な手法です。
- 独自に開発したコードで ObjectDumper を使用すると、P8 API オブジェクト内部のスナップショットを取得することができます。これは、オブジェクトが内部で保持しているデータの量を判断するのに役立ち、そのデータに対する各種のアクションの違いを確認するのにも役立ちます。
通常、ネットワーク・トレースは、一般的には、ネットワーク上を流れる生データを取り込み、それらを参照します。これを行うツールは、ネットワーク・アナライザー、ネットワーク・スニファー、またはパケット・スニファーと呼ばれることもあります。元来、スニファーは、ネットワーク・セグメントに物理的に接続する専用の装置でした。このような装置は依然として存在しますが、現在はこれと同機能のソフトウェアが存在しており、日常的に使用されています。当初、スニファーの主な用途は、ネットワークの問題をかなり詳細に診断することでした。しかし、スニファーをうまく活用することにより、分散アプリケーションのコンポーネント間の通信状況を把握することも可能です。ネットワーク・トレースは難解であり、専門家でなければ近寄りがたいものであるように思われますが、実は、普通の開発者にも非常に取っつきやすいものなのです。現在、一般に入手可能なツールには、便利なユーザー・インターフェースがあり、かなり分かりやすいマニュアル等の資料も用意されています。
実際のネットワーク・トラフィックの収集および分析に加えて、スニファーでは、トラフィックをファイルに保存することや、そうしたファイルを呼び出すことができます。これらのファイルは、一般にキャプチャー・ファイルと呼ばれます。キャプチャー・ファイルには事実上の業界標準形式がいくつか存在するので、あるスニファー・アプリケーションが生成したキャプチャー・ファイルを別のスニファー・プログラムで利用できる可能性があります (保証はありませんが)。
ネットワーク・トレース・ツールをまだお持ちでない場合は、上記の用語のどれかを Web上で検索すると、ポピュラーな代替ツールがいくつか見つかると思います。そういった中には、オープン・ソースのものや無償のものも含まれます。この記事では、Wireshark.org から入手可能な強力でポピュラーなオープン・ソースのスニファー、Wireshark を例に挙げて説明しています。この記事では、Wireshark バージョン 1.0.5 を使用しましたが、それよりも以前のバージョン、または全く異なるツールを使用した場合でも、説明する概念に大きな違いはないので、問題なく内容を理解できるはずです。この記事の目的は、読者を特定のスニファー・ツールの専門家にすることではありません。スニファー・ツールの可能性を少しだけ紹介しますので、後はみなさんでご活用いただければと思います。
ソフトウェア・スニファーの主な目的は、コンピューター・ネットワーク上に流れる通信データを、他に影響させることなく取り込むことです。これは、厳密に言うと、ネットワーク上でやりとりされる通信そのものとは異なりますが、この記事で説明するケースではこれで十分です。スニファーは、コンピューター上で稼働するプログラムにすぎません。そのため、最初に考えなければならないことは、どのコンピューター上でスニファーを実行するかということです。調査対象のコンピュータが機能的な影響を与える場合があります。スニファーは、実行しているコンピューターからの観点で調査を行います。また、2 つ以上のコンピューター間のネットワーク上の通信データを参照することは、通常はできません。実際、より複雑なケースでは、さまざまな通信の関連を調査できるように、複数のコンピューター上で同時にスニファーを実行しなければならないことがあります。しかし、それはシナリオの問題ではなく、1対1のコンピューター間の通信のみを監視すればよいのです。調査対象となるのは、API クライアントが稼働しているコンピューター、または P8 サーバーが稼働しているコンピューターになります。2つのコンピューター間だけで通信が行われていると仮定すると、スニファーをクライアント・マシンで稼働することで十分意味をなします。これにより、P8 サーバーが共用されている場合に他のクライアントとの通信が監視対象となり混乱するのを避けることができます。P8 サーバーが共用されていない場合、または P8 サーバーのアクティビティーがほとんどない場合、この選択はご自分の都合に合わせて構いません。通常、この記事のシナリオにある日常的な作業による通信では、ソフトウェア・スニファーがそのホストに与える負荷はごくわずかです。この記事では、スニファーをクライアント・マシン上で稼働していることを想定していますが、それが該当しない場合でも、別の方法でどのように監視するかは容易に解決できると思います。
対象のアプリケーションを実行していて、前述したパフォーマンス監視およびチューニングすることを想定します。そういった環境が整備されておらず、単純なアプリケーションを実行して、この記事に従って、手順を確認したい場合は、このシリーズの最初の記事から HelloDocument サンプル・アプリケーションをダウンロードしてください。スニファーをアプリケーションとともに実行するときは、次の2 つの注意点を考慮する必要があります。
- EJB トランスポートではなく、WSI トランスポートを使用します。WSI トランスポートでは、HTTPプロトコルで通信された XML が使用されます。一方、EJB トランスポートでは、使用中のアプリケーション・サーバーによって記述された形式とプロトコルが使用されます。通常、EJB トランスポートでは、圧縮形式、または簡潔なバイナリー形式を採用しています。この転送方法は、クライアント/サーバー通信には適していますが、ネットワーク・データを視覚的に検査するときには、扱いが難しくなります。
- 接続で TLS/SSL による保護を使用してはいけません。TLS/SSL では、まさにその目的によって、読み出した通信データを見ることができません。たとえ第三者が TLS/SSL 接続のセットアップ会話を完全に読み出せたとしても、その後保護されたデータ内容を復号化することはできません。スニファーには、TLS/SSL によって秘匿化されたベールを透視する魔法の力はありません。しかし、TLS/SSL 接続を保護なしで実行していると、通信データおよび未加工の認証情報等が盗聴される恐れがあります。そのため、スニファーのテストを実行している間は、機密でない情報およびアカウントを使用する必要があります。こういった作業は、多くの場合開発環境またはテスト環境で行うものなので、準備することは簡単なはずです。
これらの設定がどうなっているか、よくわからない場合、上記の点を総合的に判断すると、Content Engine への接続 URI が (HTTPS および非 HTTP URI ではなく) HTTP URI になるはずです。
一般的なパターンとしては、スニファーをオンにする、アプリケーションを実行する、スニファーをオフにする、ネットワーク・トラフィックを分析する、といった流れで調査を行います。図 1 は、取り込んだデータの冒頭部を示しています。ウィンドウ上部の各行は、取り込まれた単一の IP パケットを表しています。スニファー用語では、パケットはフレームとも呼ばれます。これは入門者にとって複雑でわかりにくい画面であると言えるでしょう。この中で、調査対象の P8 RPC データはどこにあるのでしょうか? 実際には、図 1のリストにはありません。図 1 は、最初の 1 秒に満たないネットワーク通信がリストされています。この時間では、別のウィンドウに切り替えて、アプリケーションを始動することもできません。
図1. フィルターを掛けていない未加工のネットワーク・トレース
もちろん、パケット・リストを下にスクロールして、探しているパケットを最終的に見つけることはできます。しかし、対象でないと分かっているものを除去する方法があれば素晴らしいと思いませんか?もちろん、可能です。これを行うには、2 つの方法があります。1 つ目は、無関係のフレームを非表示にするようにスニファーに指示する方法、2 つ目は、通信データの読み取り中に無関係のフレームを無視するようにスニファーに指示する方法です。1 つ目の方法では、すべての通信データを読み取っておいて、後から不要なフレームを非表示させているため、後で非表示だったフレームを再表示することができます。2 つ目の方法では、読み取り時にフレームを削除しているため、後から再表示させることはできません。2 つ目の方法の利点は、キャプチャー・ファイルのサイズがその分だけ小さくなることです。この方法では、トレースの実行時間が長い場合に違いが出てきます。ネットワーク・トレースは主に小さなセッションで行い、足りないものがあれば再度、セッションを広げてトレースを再実行できます。この選択は、調査される方の好みに合わせて構いません。ネットワーク・トレースを取り込んで他の誰かに送信することになった場合、受け取るユーザーは、取り込み時に対象をフィルターにかける (またはフィルターをかけない) 方法について、好みを持っているかもしれません。
各スニファーは、フィルター条件を設定するための独自のユーザー・インターフェースとメソッドを備えています。スニファーによっては、上記の 2 つの方法でどのようにフィルターを掛けるかについての違いがあります。ほとんどのスニファーでは、非常に簡単にフィルターを掛けることができますが、ツールの説明を正確に理解するには、いくつかの用語に慣れておく必要があります。フレームをフィルターに掛けるための有用な基準をいくつか以下に示します。これらの図は、図 1 で表示された通信データから、不要なデータを非表示にしたものです。
-
TCP のみ
WSI トランスポートの場合、すべてのP8通信データは TCP 接続でやりとりされます。したがって、すべての UDP 接続データをフィルタリングしても問題はありません。ただし、UDP 接続を保持しておくのが有用であると思われる例がひとつあります。DNS の問題解決(この記事では、行わないことを想定しています) を行う場合、大部分の DNS通信はUDP 上で行われていることが分かります。通常のLAN上には、TCP でも UDP でもない別のデータも存在します。実際、よほど独立したネットワークでなければ、そういったデータがいかに多く存在するのかを知って驚きます。これらのトラフィックを確認して、その正体を理解しようとすることは、面白くもあり、勉強にもなります (ただし、これは本シナリオには関係ありません)。図 2 は、図 1 と同じネットワーク・トレースの内容ですが、TCP フレームのみを表示しています。
図 2. フィルターに掛けて TCP のみが示されたネットワーク・トレー
-
ターゲット・ホストのみ
トレースを TCP フレームに限定しても、依然として無関係のフレームが多数表示されます。実際、図 2 では、関係のあるフレームはなく、最初の 2 秒未満のトラフィックが依然として最初の画面に表示されています。ここで調査の対象となるので、2つのコンピューター間の通信です。クライアント・マシン上でスニファーを実行しているので、P8サーバーとの通信フレームのみが表示されるようにトレースをフィルターに掛けます。このフィルターを設定するときは、ターゲット・ホストが送信および受信する両方のパケットを対象とすることに注意します。そうしないと、通信の半分しか表示されません。この例のシナリオでは、ターゲット・ホストの IP アドレスは 9.39.109.62 です。図 3 は、宛先 IP アドレスでさらにフィルターに掛けたネットワーク・トレースを示しています。スニファー・ツールおよび設定によっては、IP アドレスではなくホスト名を使用して、同じことを行える場合があります。
図 3. フィルターに掛けてターゲット・ホストのみが示されたネットワーク・トレース
-
ターゲット・ポートのみ
アプリケーションが使用している特定の宛先ポートのみが示されるようにさらにフィルターを掛けることができます。この場合も同様に、ターゲット・ポートへ向かうパケット、またはターゲット・ポートから出るパケットを指定します。この例のシナリオでは、ポート番号は 9080 です。これは、P8 WSI トランスポートに対する WebSphere® のデフォルト・ポート番号です。図 4 は、宛先ポートを指定したフィルターにより抽出されたトレースを示しています。
図 4. フィルターに掛けてターゲット・ポートのみが示されたネットワーク・トレース
補足として、Info列にターゲット・ポート glrpc を呼び出しているように見える行が存在するのかを説明します。Wiresharkのデフォルトでは は、ユーザーの便宜のために表示対象にラベルを付けようとします。Internet Assigned Numbers Authority (IANA) によって保守されている公式のポート番号レジストリー (『参考文献』を参照) では、TCP ポート 9080 は Groove GLRPC と指定されています。Wireshark がラベルを付けるのはそのためです。次の項目で参照するように、これは単なるラベルであるので、気にする必要はありません。図 4 の中段のウィンドウで、glrpc が実際にポート 9080 であることを確認できます。
- Wireshark は、実際に処理しているプロトコルを認識している場合は、多くのタイプのプロトコル固有のデコードを実行できます。ポート 9080 のトラフィックを HTTP として解釈するために、「Analyze」>「Decode as」をクリックします。図 5 を参照してください。
図 5. Wireshark のデコード
図 6 は、HTTP デコードを適用したトレースです。パケットによっては、ポート 9080 を使用する通信にも関わらず、TCP としてマークされ続けているものがあります。これは、パケット内に実際の HTTPプロトコル通信データが含まれていないからです。これらのパケットは、ネットワーク応答パケットおよびその他の TCP通信データです。理論的には、ソース・ポートが 9080 であるいくつかのパケットを誤って HTTP としてマークすることもあり、その場合は、宛先ポートが9080である通信のみが関係します。実際には、これが問題になることはほとんどありません。
図 6. HTTP としてデコードされたネットワーク・トレース
トレースをフィルターに掛けて、関心のあるパケットのみを取り出しました。ここで、何を見るべきでしょうか? 図 6 は、295番目のフレーム が選択されており、その詳細が下部の 2 つのウィンドウに展開表示されています。P8 Content Engine サーバーの Web サービス・リスナーからの応答が成功していることが、表示されている内容から推定できます。スニファー・ツールは、パケット・レベルで対象を表示します。そのため、すべての IP および TCP ヘッダー・フィールドの値と HTTP 通信自体も読み取ることができます。それはそれで、非常に重宝するのですが、HTTP通信は多数の TCP パケットを含む場合があります。少なくとも、1 つの要求パケットと1 つの応答パケットが存在することになるのですが、これらの要求パケット、応答パケットのいずれかまたは両方が後続パケットに分割される可能性があります。通常は HTTP 通信に関心があるので、その他のすべてのパケット・通信以外が読み取れればよいでしょう。また、要求に対応する応答を、対比させながら視覚的に表示できればなお便利です。Wireshark には、それを行う機能があります。あるHTTP パケットを選択し、右クリックして、「Follow TCP stream」を選択すると、図 7 に示すようなウィンドウがポップアップ表示されます。
図 7. TCP ストリームの追跡
図 7 は少し読みにくいかもしれないので、これと同じ情報を最小限の変更のみを加えてリスト 1 に示します。ページ幅の制限に合わせるために、XML の連続行の一部とその他の長い行を任意の点で切断しています。埋め込まれた復帰文字は、連続する 2 文字 ^M に変換し、見やすくしています。
リスト 1. 図 7 からのテキスト
POST /wsi/FNCEWS40MTOM/ HTTP/1.1^M
User-Agent: IBM FileNet P8 CE Java API (X.Y.Z / dapNNN.MMM)^M
Date: Sat, 24 Jan 2009 00:58:55 +0000^M
Content-Type: application/soap+xml; charset=UTF-8^M
Host: MyCEServer:9080^M
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2^M
Connection: keep-alive^M
Transfer-Encoding: chunked^M
^M
29a^M
<?xml version="1.0" encoding="UTF-8"?><e:Envelope
xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.filenet.com/ns/fnce/2006/11/ws/schema"
xmlns:e="http://www.w3.org/2003/05/soap-envelope"
xmlns:d="http://www.w3.org/2001/XMLSchema">
<e:Header><Security><UsernameToken>
<Username>user1234</Username><Password>pass9876</Password>
</UsernameToken></Security><Localization><Locale>en-US</Locale>
<Timezone>-08:00</Timezone></Localization></e:Header>
<e:Body><GetObjectsRequest><ObjectRequest id="1" cacheAllowed="1">
<SourceSpecification i:type="ObjectReference" classId="Domain">
</SourceSpecification></ObjectRequest></GetObjectsRequest>
</e:Body></e:Envelope>^M
2^M
^M
0^M
^M
HTTP/1.1 200 OK^M
Server: Systinet Server for Java/6.5.4 (Java/1.5.0; Windows Server 2003/5.2
build 3790 Service Pack 2; build SSJ-6.5.4-20060829-1824)^M
Content-Type: multipart/related;
boundary=----------MULTIPART_BOUNDARY_11f0627864bcdb----------;
type="application/xop+xml"; start-info="application/soap+xml";
start="<32bb849648573a7a-1c0d723b50a78063-2392a2760337fe0f-f87db666f64901d6>"^M
Content-Language: en-US^M
Transfer-Encoding: chunked^M
Date: Sat, 24 Jan 2009 01:01:06 GMT^M
^M
d14^M
^M
------------MULTIPART_BOUNDARY_11f0627864bcdb----------^M
Content-Transfer-Encoding: binary^M
Content-Type: application/xop+xml; type="application/soap+xml"; charset=UTF-8^M
X-WASP-Message-ID: 101-bcH4nI/ftBJRilNZkDiIkg==^M
Content-ID: <32bb849648573a7a-1c0d723b50a78063-2392a2760337fe0f-f87db666f64901d6>^M
^M
<?xml version="1.0" encoding="UTF-8"?>
<e:Envelope xmlns:e="http://www.w3.org/2003/05/soap-envelope"
xmlns:d="http://www.w3.org/2001/XMLSchema"
xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
xmlns:wn0="http://www.filenet.com/ns/fnce/2006/11/ws/schema">
<e:Body><GetObjectsResponse
xmlns:n0="http://www.filenet.com/ns/fnce/2006/11/ws/MTOM/schema"
xmlns="http://www.filenet.com/ns/fnce/2006/11/ws/schema">
<ObjectResponse id="1" i:type="SingleObjectResponse">
<Object i:type="ObjectValue" classId="Domain"
objectId="{064735FF-3160-45D3-8258-ABA3AAE8F204}" updateSequenceNumber="5"
accessAllowed="459267"><Property i:type="UnevaluatedCollection"
propertyId="RenditionEngineConnections" collectionType="Enum"/>
<Property i:type="UnevaluatedCollection" propertyId="ContentCacheAreas"
collectionType="Enum"/><Property i:type="SingletonId"
propertyId="Id"><Value>{064735FF-3160-45D3-8258-ABA3AAE8F204}</Value>
</Property><Property i:type="UnevaluatedCollection" propertyId="FixedContentDevices"
collectionType="Enum"/><Property i:type="SingletonBinary"
propertyId="PublicKey">
<Value>MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCshauwe2PIcNhCLGXhvL40DDFnNxcu1Ua
jWNe/KQ4+tQ5solDS+JGPcv1m9sOIaQpxGh2uq/luGvuZh9EiHdfJMlfVAxkjdc7ZHI/oCtaook4CCYMx7Xx
ELBu4kJAkWEr324PXfHRgqlHmv7dXkO2NGvTfa+bDypnzPDEKfEDKjwIDAQAB</Value>
</Property><Property i:type="UnevaluatedCollection"
propertyId="PEConnectionPoints" collectionType="Enum"/>
<Property i:type="UnevaluatedCollection" propertyId="AddOns"
collectionType="Enum"/><Property i:type="UnevaluatedCollection"
propertyId="ObjectStores" collectionType="Enum"/><Property
i:type="SingletonString" propertyId="EncryptionAlgorithm">
<Value>RSA/NONE/PKCS1PADDING</Value></Property><Property
i:type="UnevaluatedCollection" propertyId="Sites"
collectionType="Enum"/><Property i:type="UnevaluatedCollection"
propertyId="SubsystemConfigurations" settable="1"
collectionType="List"/><Property i:type="SingletonString"
propertyId="Name" settable="1"><Value>WAS_SQL</Value>
</Property><Property i:type="UnevaluatedCollection"
propertyId="DirectoryConfigurations" settable="1" collectionType="List"/>
<Property i:type="SingletonObject" propertyId="DefaultSite"
settable="1"><Value i:type="ObjectReference" classId="Site"
objectId="{07539A4C-DD66-492B-8A0B-BC2363C9BDB9}"/></Property>
<Property i:type="SingletonObject" propertyId="VerityDomainConfiguration">
<Value i:type="Unevaluated"/></Property><Property i:type="UnevaluatedCollection"
propertyId="IsolatedRegions" collectionType="Enum"/><Property
i:type="UnevaluatedCollection" propertyId="PermissionDescriptions"
collectionType="List"/><Property i:type="UnevaluatedCollection"
propertyId="MarkingSets" collectionType="Enum"/><Property
i:type="SingletonObject" propertyId="ClassDescription"><Value
i:type="ObjectReference" classId="ClassDescription"
objectId="{FCEEED8B-AE17-441C-AD04-56E560255331}"/></Property><Property
i:type="UnevaluatedCollection" propertyId="Permissions" settable="1"
collectionType="List"/></Object></ObjectResponse>
</GetObjectsResponse></e:Body></e:Envelope>^M
3d^M
------------MULTIPART_BOUNDARY_11f0627864bcdb------------^M
^M
0^M
|
こういった通信内容を見たことがない方は、リスト 1 の要求と応答とともに現れる独立した数値 (29a、d14 など) に疑問を感じるかもしれません。これらの数値は、HTTP通信が適当なサイズでブロックに区切られ(この固まりをチャンクとも呼びます)て行われるために表示されます。これは、次のブロックのバイト数を16 進数で表現したものです。要求、応答の両ケースで、最終的なブロックの最後にセットされる数値0 バイトを意味していることに注意してください。リスト 1 では、形式の微調整のために数値が正確に計算されません。HTTP通信のブロックについて詳しく知りたい場合は、RFC-2616 の HTTP 1.1 仕様 (『参考文献』を参照) をご覧ください。ここでは、これらの数値についての説明は省略します。
この例のシナリオにはありませんが、複数の要求と応答のペアが同じ TCP ストリームに存在する場合があります。これも、永続 HTTP 接続が使用されているためです。HTTP通信の観点では複数の要求と応答が存在しますが、TCP の視点では会話が 1 つしか存在しません。サーバーから例外が送信される場合は、特殊なケースになります。例外が SOAP 失敗としてクライアントに返され、通常は、SOAP 失敗の送信後に通信が切断されます。API レベル上では、こういった失敗を区別することはありませんので、ネットワーク・トレースを参照していなければ、この違いを認識することもないでしょう。
詳細な通信トレースがあるとして、どうやって読み取っていけばよいでしょうか? 既にお気づきだとは思いますが、Content Engine Web Services (CEWS) の Web Services Definition Language (WSDL) ファイルとして、XMLファイルで記述されます。決まり切った検索(特定のプロパティーの有無、オブジェクトの数など)を行う場合、すべての WSDL ファイルの微妙な違いを完全に理解しなくても、XML から情報を取り出すことが比較的簡単です。ただし、より読みやすい形式で XML を表示した方がよいと思われるでしょう。これを xml という拡張子でファイルに保存すれば、大部分の Web ブラウザーは、構造化された読みやすい形式でそのファイルを適切に表示します。図 7 およびリスト 1 は、ヌルのドメイン名とプロパティー・フィルターを指定して Factory.Domain.fetchInstance を呼び出した際の RPC通信の中身です。この応答として、複数のプロパティーを含むドメイン・オブジェクトが戻ります (これらの大部分は、理解する必要がないものです。これについては、プロパティー・フィルターに関する将来の記事で説明します)。
利点
- ネットワーク・トレースにより、ネットワーク上の全データが完全かつ忠実に読み取ることができます。その内容が近似値であったり、一部が欠落しているといった、心配は不要です。
- ネットワーク・トレースは、他への影響を及ぼすものではでありません。ネットワーク・トレースは、アプリケーションの構成に手を入れることもなく、いつでも実行することができます (ただし、『欠点』セクションを参照)。
- 一般にパフォーマンスへの影響は無視できます。ソフトウェア・スニファーは、関連するコンピューターのいずれかで動作しますが、リソース消費の点では非常に軽量である傾向があります。
欠点
- P8 Content Engine Java および .NET API の対話のネットワーク・トレースを行う特定のケースでは、WSI トランスポートを使用して (.NET API の場合は常に該当)、TLS/SSL 暗号化をはずすと、意味のあるものがすべて表示される可能性があります。その意味で、アプリケーションの現実的な構成を必ずしも使用するとは限りません。ただし、コーディング手法によるパフォーマンスの問題点を確認する目的では、問題になりません。
- 同様に、暗号化やその他の要素が原因で、仮想プライベート・ネットワーク (VPN) 上を移動するネットワーク・トラフィックの有意義なトレースを取得できない場合があります。これは、在宅勤務者やその他の遠隔作業者にとって非常に不便です。
- ここまでの図を見て分かるように、ネットワーク・トレースの乱雑さには、慣れるまで少し抵抗があるでしょう。また、非常に技術的な障壁を感じることもあります。実際は、すぐに慣れて、必要なものを表示するようにネットワーク・トレース・ツールを構成できるようになります。
- ネットワーク・トレースを長時間、実行すると、取り込んだファイルが非常に大きくなる場合があります。ネットワーク・トレースを他の種類の問題の解決に使用しているときは、これが問題になる傾向があります。どのケースでも、取り込みフェーズでフィルター・ルールを設定すれば、それを緩和できます。
P8 Content Engine は、CE サーバーと Java API の両方で、ポピュラーなオープン・ソース・パッケージである log4j (Apache Software Foundation プロジェクト。『参考文献』を参照) を使って、トレース・ログを取得します。このセクションでは、このパッケージの構成と利用方法について説明します。
ネットワーク・トレースの場合と同様、API トレース・ログは、CE サーバー上、または API クライアントから取得できます。どちらかを自由に選択することができますが、CE サーバーが他からも利用されていたり、負荷が高い状態である場合は、無関係のデータが大量にログとして記録されることを考慮してください。.NET API の場合は、サーバー側でトレースを取得する必要があります。この記事のシナリオでは、ユーザーが Java API を利用し、でクライアント側でAPI ログを取得するものと想定しています。
FileNet Enterprise Manager (FEM) には、サーバー側でログを取得する設定ができるユーザー・インターフェースを用意していますが、クライアント側でのAPI ログの設定は、構成ファイルにて行います。log4j 構成ファイルは、XML 形式 (慣例的に log4j.xml という名前です) で記述することも、Java プロパティー・ファイル形式 (慣例的に log4j.properties という名前です) で記述することもできます。これらの形式について詳しくは、log4j Web サイト (『参考文献』を参照) をご覧ください。XML 形式は、柔軟性が高く機能も豊富ですが、プロパティー・ファイル形式による記述の方が若干簡単に設定可能です (個人の技術レベルに依存します)。IBM FileNet Content Manager 製品には、サンプルの構成ファイル (log4j.properties.client) が用意されていますので、このファイルを編集し log4j.properties というファイル名に変更してログ設定することができます。リスト 2 は、IBM FileNet Content Manager 4.5 出荷時のこのファイルの内容を示しています (この記事の形式に合わせるために、少しだけ変更が加えられています)。
リスト 2. サンプルの log4j.properties.client
################################################################################
# Root logger
################################################################################
log4j.rootLogger=off, FileNetNullAppender
################################################################################
# Appenders
################################################################################
#=== FileNetNullAppender
log4j.appender.FileNetNullAppender=org.apache.log4j.varia.NullAppender
#=== FileNetConsoleAppender
log4j.appender.FileNetConsoleAppender=org.apache.log4j.ConsoleAppender
log4j.appender.FileNetConsoleAppender.layout=org.apache.log4j.PatternLayout
log4j.appender.FileNetConsoleAppender.layout.ConversionPattern=%d %5p [%t] - %m\r\n
#=== FileNetErrorAppender
log4j.appender.FileNetErrorAppender=org.apache.log4j.FileAppender
log4j.appender.FileNetErrorAppender.File=/p8_api_error.log
log4j.appender.FileNetErrorAppender.layout=org.apache.log4j.PatternLayout
log4j.appender.FileNetErrorAppender.layout.ConversionPattern=%d %5p [%t] - %m\r\n
#=== FileNetTraceAppender
log4j.appender.FileNetTraceAppender=org.apache.log4j.FileAppender
log4j.appender.FileNetTraceAppender.File=/p8_api_trace.log
# This is the layout that the TraceLoggingConfiguration framework on the server uses.
# To use this layout , jace.jar must be present in the classpath.
#log4j.appender.FileNetTraceAppender.layout=com.filenet.apiimpl.util.TraceLayout
# Comment out the following lines if using the FileNet TraceLayout
log4j.appender.FileNetTraceAppender.layout=org.apache.log4j.PatternLayout
log4j.appender.FileNetTraceAppender.layout.ConversionPattern=%d %5p [%t] - %m\r\n
#=== FileNetErrorRollingAppender
log4j.appender.FileNetErrorRollingAppender=org.apache.log4j.RollingFileAppender
log4j.appender.FileNetErrorRollingAppender.File=/p8_api_error.log
log4j.appender.FileNetErrorRollingAppender.MaxFileSize=100MB
log4j.appender.FileNetErrorRollingAppender.MaxBackupIndex=1
log4j.appender.FileNetErrorRollingAppender.layout=org.apache.log4j.PatternLayout
log4j.appender.FileNetErrorRollingAppender.layout.ConversionPattern=%d %5p [%t] - %m\r\n
#=== FileNetTraceRollingAppender
log4j.appender.FileNetTraceRollingAppender=org.apache.log4j.RollingFileAppender
log4j.appender.FileNetTraceRollingAppender.File=/p8_api_trace.log
log4j.appender.FileNetTraceRollingAppender.MaxFileSize=100MB
log4j.appender.FileNetTraceRollingAppender.MaxBackupIndex=1
# This is the layout that the TraceLoggingConfiguration framework on the server uses.
# To use this layout , jace.jar must be present in the classpath.
#log4j.appender.FileNetTraceRollingAppender.layout=com.filenet.apiimpl.util.TraceLayout
# Comment out the following lines if using the FileNet TraceLayout
log4j.appender.FileNetTraceRollingAppender.layout=org.apache.log4j.PatternLayout
log4j.appender.FileNetTraceRollingAppender.layout.ConversionPattern=%d %5p [%t] - %m\r\n
################################################################################
# Error Loggers:
#
# Set log level to either one of off/fatal/error/warn/info.
# Child logger's value overwrites parent logger's value.
# If a logger is not specified, it inherits its value from its parent.
# By default, error logging is set to level ERROR.
################################################################################
# Don't comment out the following line since it has appenders.
log4j.logger.filenet_error = error, FileNetConsoleAppender, \
FileNetErrorRollingAppender, FileNetTraceRollingAppender
#=== SubSystem: api
# Uncomment to set error logging level to WARN.
#log4j.logger.filenet_error.api = warn
################################################################################
# Trace loggers:
#
# Setting log level to "off" will turn off trace logging.
# Setting log level to "debug" will turn on trace logging.
#
# Child logger's value overwrites parent logger's value.
# If a logger is not specified, it inherits its value from its parent.
# By default, trace logging is off.
#
# The general message trace logging has a hierarchy of three detail levels represented
# with hierarchical logger names:
# ...detail.moderate.summary -- Enables a summary level of tracing
# ...detail.moderate -- Enables a moderate level of tracing
# ...detail -- Enables the most detailed level of tracing
#
# Uncomment corresponding lines to turn tracing on. To enable trace logging
# for all subsystems and all flags, set the level of the filenet_tracing
# logger to debug.
#
# Here are the trace flags used for each sub system. For message tracing, enable the line
# corresponding to the desired detail level.
# log4j.logger.filenet_tracing.<SubSystem>.timer = debug
# log4j.logger.filenet_tracing.<SubSystem>.detail.moderate.summary = debug
# log4j.logger.filenet_tracing.<SubSystem>.detail.moderate = debug
# log4j.logger.filenet_tracing.<SubSystem>.detail = debug
#
# For example:
# log4j.logger.filenet_tracing.api.detail.moderate = debug
################################################################################
# Don't comment out the following line since it includes an appender.
log4j.logger.filenet_tracing = off, FileNetTraceRollingAppender
#=== SubSystem: api
# Uncomment one or more lines to enable tracing.
#log4j.logger.filenet_tracing.api = debug
#log4j.logger.filenet_tracing.api.timer = debug
# Remove the comment corresponding to the desired detail level
#log4j.logger.filenet_tracing.api.detail.moderate.summary = debug
#log4j.logger.filenet_tracing.api.detail.moderate = debug
#log4j.logger.filenet_tracing.api.detail = debug
|
この構成ファイルの量と中身を見ただけでも、サンプルの構成ファイルが非常に柔軟であり、多数のロギング・シナリオが可能であることがわかるでしょう。log4j についてさらに詳しく知りたい場合は、log4j の文書を探し、あらゆる構成方法について調査するのがよいでしょう。この記事で説明しているのは、数例の設定だけで、ほとんどは、ファイルの終わりの方にあるいくつかのコメント行として記述されています (リスト 3 に再掲しています)。API トレース・ロギングには、要約、標準、詳細という 3 つの冗長レベルがあり、それぞれのレベルは、構成ファイルの該当行のコメントを外すことで有効になります。この記事では、要約レベルと標準レベルの例を示しますが、これは、ネットワーク・トレースの例で説明したドメインの取得 (getObjectsRequest) と、存在しないオブジェクト・ストア内でのフォルダー作成の失敗 (executeChangesRequest) という 2 つの操作におけるものです。この記事では、詳細トレース・レベルの例は示しません。ログに追記される内容は、この議論では特に気にする必要がないからです。ここで重要なのは、トレース・ログ出力の主な目的は、さまざまな問題を解決するために支援することです。ログに記録される特定のメッセージと情報は、異なるリリースでも同じものであるという保証はなく、通常、この変更は文書化されていません。メッセージそのものは、頻繁に変更されます。
リスト 3. API トレースの冗長性
# Remove the comment corresponding to the desired detail level #log4j.logger.filenet_tracing.api.detail.moderate.summary = debug #log4j.logger.filenet_tracing.api.detail.moderate = debug #log4j.logger.filenet_tracing.api.detail = debug |
API トレース・ログは、ファイル /p8_api_trace.log に出力されます。この場所を変更したい場合は、log4j 構成ファイル内のこのファイル名の出現箇所をすべて探して、別の場所に変更することができます。この構成では、log4j でローリング・ファイル・アペンダーと呼ばれるものが使用されます。これは、ファイルが特定のサイズに達するまで、ログ行がファイルに追記され、ログ・ファイル全体が指定されたのサイズに達すると、そのファイルは別名で保管され、新しいファイルに記録されるようになります。これは、トレースを長時間実行するときに非常に便利です。ただし、プログラムを少しずつ変更してパフォーマンス・チューニングを行うような反復開発では、アペンダーが定義されているセクションに行 log4j.appender.FileNetTraceRollingAppender.Append=false を指定するのがよいでしょう。その他のアペンダー定義セクションにあるアペンダーにも同様の変更を必ず加えてください。この行を追加すると、log4j は、アプリケーションが新しく実行されるたびにログ・ファイルの内容を削除して、新たにログ・ファイルを作り直します。この指定をしないと、ログ・ファイルの先頭を見ても、内容が更新されていない理由がすぐに分からなくなります。
通常、log4j は、Java classpathを検索してその構成ファイルを探します。特定の構成ファイルを指すように設定できる Java プロパティーも存在します。スタンドアロン Java アプリケーションで API トレース・ログを取得する場合は、両方のメカニズムを簡単に使用できます。J2EE アプリケーション・サーバー内で実行している場合は、(アプリケーション・サーバーのclasspathに複数の値がセットされていることが原因で) log4j の構成ファイルをうまく特定できない可能性があります。このような場合は、空の JAR ファイルを作成し、この JAR ファイルの (サブディレクトリー内ではなく) 最上位レベルに log4j.properties を配置します。このJARファイルの名前は、wjc.jar にします。ただし、他の JAR ファイル名と競合しなければ、名前は重要でありません。通常は、wjc.jar およびアプリケーションで使用するその他の JAR ファイルを WEB-INF/lib/ などの場所に配置します。アプリケーション・サーバーは、これらのその他の JAR ファイルを見つけた場合に、wjc.jar も見つけることになります。通常のJavaアプリケーションに関してもこれと同じ手法を使用できますが、log4j.properties を wjc.jar に含めると、編集がより難しくなります。
サンプルの構成ファイルでは、タイムスタンプを含むロギング出力の形式、ログ・レベル (この例のシナリオでは、常にデバッグ)、およびスレッド名が指定されます。簡略化のため、これらの列は、サンプルのログには出力されていません。編集上の理由で長い行を故意に切断している箇所では、継続される行の末尾に円記号が、継続行の行頭に空白で表示しています。読みやすくするための改行箇所は、行に 3 つのドットがある空白が示されています。
- 要約 :
リスト 4. 要約の API トレース
Configuration for:Config.AutoRefreshEnabled value:null applied
. . .
getObjects request batch-size=1 ObjectReferences \
[classId=Domain&objectId=null]
. . .
getObjects response elapsed=5272 batch-size=1
executeChanges request batch-size=1 ObjectReferences [classId=ObjectStore&\
objectId=MissingObjectStore]
executeChanges elapsed=943 exception: \
com.filenet.api.exception.EngineRuntimeException: \
Requested item not found. Object store MissingObjectStore not found.
|
- 標準 :
リスト 5. 標準の API トレース
Configuration for:Config.AutoRefreshEnabled value:null applied
. . .
<getObjectsRequest cacheAllowed="true" correlationId="1">
<objectReference type="GlobalIdentity" classIdentity="Domain" rid="2"/>
</getObjectsRequest>
. . .
<getObjectsResponse elapsed="4224" correlationId="1">
<value type="Domain" updateSequenceNumber="5" accessAllowed="459267">
<objectReference type="GlobalIdentity" classIdentity="Domain" \
identity="{064735FF-3160-45D3-8258-ABA3AAE8F204}" rid="3"/>
<properties type="Properties">
<property name="Permissions">
<value type="UnevaluatedPropertyValue" propertyName="Permissions">
<parent ref="3"/>
</value>
</property>
<property name="FixedContentDevices">
<value type="UnevaluatedPropertyValue" propertyName="FixedContentDevices">
<parent ref="3"/>
</value>
</property>
<property name="Id">
<value>{064735FF-3160-45D3-8258-ABA3AAE8F204}</value>
</property>
. . .
<property name="ObjectStores">
<value type="UnevaluatedPropertyValue" propertyName="ObjectStores">
<parent ref="3"/>
</value>
</property>
</properties>
</value>
</getObjectsResponse>
<executeChangesRequest refresh="false" correlationId="0" updateSequenceNumber="null">
<pendingAction type="Create" classId="Folder" \
objectId="{3C19B4FE-B835-44F8-AB94-AB4F40997FB1}"/>
<objectReference type="GlobalIdentity" classIdentity="ObjectStore" \
identity="MissingObjectStore" rid="3"/>
<modifiedProperties type="Properties">
<property name="Parent">
<value type="ObjectByPath" classIdentity="Folder" path="/" rid="6">
<objectStore ref="3"/>
</value>
</property>
<property name="FolderName">
<value>wjc</value>
</property>
</modifiedProperties>
</executeChangesRequest>
<executeChangesResponse type="EngineRuntimeException" elapsed="845" rid="1">
com.filenet.api.exception.EngineRuntimeException: E_OBJECT_NOT_FOUND: \
Requested item not found. Object store MissingObjectStore not found. errorStack={
at com.filenet.engine.gcd.GCDHelper.getObjectStoreId(GCDHelper.java:347)
. . .
at com.ibm.ws.util.ThreadPool$Worker.run(ThreadPool.java:1469)
</executeChangesResponse>
|
要約レベルの API トレース・ログは、処理されている RPC とそれに要した時間を確認する場合に便利です。RPC の内容についてさらに詳しく知りたい場合は、標準レベルまたは詳細レベルが適切です。RPC は、常に要求と応答のペアとして表示されます。要求および応答の XML は、ネットワーク・トレースのサンプルに見られるものとよく似ていますが、完全にはWSDL に準拠していません。一部、近似と単純化が使用されています。API トレース・ログでは、XML は読みやすい形式で表示されます。そのため、通信されているデータの大部分の意味を容易に判別することができます。通常、パフォーマンス・チューニングでは、必要なタスクをアプリケーションで実行できるようにしながら、最初に RPC 数を削減し、次に受け渡されているプロパティー数を最小にします。リスト 4 および 5 では、これらの両方を簡単に見つけられるはずです。
利点
- API トレース・ロギングは、WSI および EJB の両方のトランスポートとともに使用できます。
- API トレース・ロギングは、TLS/SSL または VPN の使用の影響を受けません。
- API トレース・ロギング出力は、ネットワーク・トレース・ファイルより若干コンパクトです。また、ユーザーがより読みやすいように XML形式で出力されるように設定されています。
欠点
- API トレース・ログには、パフォーマンスへの影響を与える可能性があります。トレース・ロギングの要約レベルでは、パフォーマンスへの影響は最小になります。標準レベルおよび詳細レベルでは、パフォーマンスへの影響は大きくなる場合があります。標準レベルおよび詳細レベルでは、RPC タイミング番号が若干誇張されます。
- API トレース・ログでは、構成を混乱させる厄介な要素があります。特に複雑なアプリケーション環境では、J2EE アプリケーション・サーバーで設定される構成ファイルを取得することが厄介な場合があります。
- API トレース・ログ出力は、ネットワーク上で実際に送信された内容の要約にすぎません。多くの目的では、要約で十分です。
- API トレース・ログ出力には、エンベロープまたはヘッダー情報がごくわずかしか記録されません。そのため、情報を丹念に調べることが多少困難な場合があります。つまり、すべての問題判別をする際の最適ツールであるとは限りません。
- API トレース・ログは、診断ツールとしての性質を持っています。また、その出力内容は、ユーザーの実行環境には存在しないソース・コードやその他の情報に関連する内容が含まれます。そのため、API トレース・ログ出力によっては、実際にはより混乱してしまう場合があります。例えば、API でその環境における現象を調べていると、実行環境では見つからない情報が多数ログに記録されます。実際には、これらのメッセージはまったく正常のものです。ただし、このような事柄とより重要な API トレース・ログ・メッセージとを区別するのは、困難な場合があります。
対話式デバッガーを使用してアプリケーションを処理すると、オブジェクト状態に関する無数の詳細情報と、各種のメソッド呼び出しの結果を調べることができます。しかし、複数のオブジェクトの内容を出力するユーティリティーを使用したいときもあるでしょう。ソース・コードで提供されるサンプル・クラス ObjectDumper を利用すれば、それが可能です (『ダウンロード』を参照)。このコードは、そのまま使用することも、実行環境にあわせて変更することも可能です。ObjectDumper は、公開された API だけを使って作成されています (Content Manager 4.5.0 の場合。ただし、古いリリースでも同様に機能するはずです)。このコードには、「非公開」、つまり文書化されていないメソッドは使用していません。このコードは、各種オブジェクトに定義される toStringメソッドの 出力に依存しています。そのため、ありふれた注意ですが、これらの形式と内容はリリースごとに変更される場合があります。特定の情報に対して toStringメソッド を使用する理由の一つとして、ObjectDumper 自身により、その内容をダンプするオブジェクトの値を絶対に変更させないようにしているためです。オブジェクトの内容が変更されるのであれば、問題判別の方法として、非常に大きな問題を持っていることになってしまいます。P8 Content Engine Java と .NET API は主に、公開されたインターフェースと、非公開のクラスから構成されます。そのため、これらの非公開の実装クラスへの参照が多数あります。これらも変更されることがあるので、独自のコードでは、ObjectDumper の場合と同様、公開されたクラスとメソッドのみを使用するようにします。
ObjectDumper では、カスタマイズできる範囲が限られます。しかし、サブクラス化してメソッドをオーバーライドすれば、より多くの動作変更できるようになっています。使用する OutputStream を設定できるコンストラクターがありますが、OutputStream が設定されない場合は、デフォルトで System.out に出力されます。また、出力のすべての行に対して、行頭に出力する文字列タグを設定することもできます。これは、複数のオブジェクト・ダンプを実行する場合の、ラベル付け技法として有効です。デフォルトは、空のストリングです。
オブジェクトをダンプするには、汎用的な dump 関数を呼び出します。この dump 関数は、そのオブジェクトとすべての論理的な下位オブジェクトに関する内容を出力します。オブジェクト・ツリーを探索するときは、深さカウンターが維持されます。行単位で、深さレベルごとに 空白2文字分だけインデントされます。dump(Object, int) のソース・コードを参照すると、その大部分が instanceof テストのカスケードであり、特定タイプのオブジェクトをダンプ処理する特定の関数呼び出しが続いていることが分かります。これらの関数内で、さらに下位のオブジェクトをダンプするときは、再帰的に汎用 dump 関数を呼び出します。これらの特定タイプのオブジェクトのいずれかの dump 関数を変更する場合は、サブクラスでこれらの関数をオーバーライドできます。ObjectDumper が特定タイプのオブジェクトを理解できない場合は、汎用の toString メソッドを使用します。例として、ワーカー・メソッド dumpEngineSet をリスト 6 に示します。
リスト 6. dumpEngineSet メソッド
/**
* Dumping an EngineSet is slightly tricky because we don't know anything about
* the state of any Iterator or PageIterator being used elsewhere. In general,
* we can't even say if the collection is paged or if we have all of the items
* already. Consequently, we just dump information about the first page in the
* collection. The PageMark is opaque, so we just rely on the generic toString
* output in case it reveals anything useful.
*/
protected void dumpEngineSet(EngineSet engineSet, int depth) throws IOException
{
PageIterator pi = engineSet.pageIterator();
pi.nextPage();
PageMark pm = pi.getPageMark();
Object[] pageItems = pi.getCurrentPage();
String summary = "n=" + pageItems.length + " " + pm;
os.write(summary.getBytes());
os.write(NEWLINE_AS_BYTES);
dump(pageItems, depth);
}
|
クラスの最も複雑なケースでは、個々の Property オブジェクトを扱う場合があります。API で特定の問題が解決する特殊なケースが多数あります。最初に、プロパティーは、単一の値を表すことも、値の集合を表すこともできます (これは、そのプロパティーのメタデータ定義によって決まります)。Content Engine 用語では、多値プロパティーは MVP と略されることがあります。プロパティーは、スカラー型、つまり整数やストリングのような単純な型にすることも、オブジェクト値にすることもできます。Content Engine 用語では、オブジェクト値プロパティーは OVP と略されることがあります。
API のProperty オブジェクトは、PropertyState というプロパティ持っています。これは、単純にAPI オブジェクトの状態を反映するものにすぎず、リポジトリー内に存在しているので、実際のプロパティーに何ら影響を与えるものではありません。PropertyState を管理している主な目的は、あるAPI中のプロパティーがいつ評価対象外または参照になったかを追跡することです。参照は、別のオブジェクトを識別する情報をその内部に持つプロパティー値ですが、その別のオブジェクトに関して識別情報以外の情報は現在 (API 内で) 認識されません。評価対象外のプロパティー値に関して、API は現在、そのようなプロパティーが存在するという事実以外は何も認識しません。通常、評価対象外のプロパティーは、MVP です。API は、プロパティーがこの特定のインスタンスについて非ヌル値を持つかどうかも認識しません。これらの概念は少し複雑ですが、特に指示がなければできるだけ効率的に処理を実行するという Content Engine サーバーの指針のために両方の概念が生じています。参照および評価対象外のプロパティー値は、必要なときはいつでも完全に解決できます。これは、パフォーマンス・チューニングの一環として最適な時点で行われます。将来の記事では、これについてさらに詳しく説明します。
Property オブジェクトの値は、別の API オブジェクトを指すことができます。そしてこの API オブジェクトは、その他のオブジェクトを指すプロパティーを持つことができます。これらの指されたオブジェクトが共用 API オブジェクト・インスタンスになることは珍しくありません。これが起こるたびにダンプすることは、特に有益ではありません。ObjectDumper は、既に参照したオブジェクトを内部的に追跡します。このオブジェクトを再度参照すると、以前のオカレンスへの特殊な参照を出力するだけで、下位オブジェクトを再帰的にダンプしません。これは、単なる便宜と整然さの問題ではありません。API オブジェクト間の参照は、相互参照される場合があります。そのため、サイクルを参照するときに、以前参照したオブジェクトを追跡すると、無限の再帰も防止されます。
このシリーズの第一部で扱った HelloDocument プログラムに小さな変更を加えて、次の出力が生成されました。コードの変更をリスト 7 に示します。Content Engine サーバーに更新を送信する前および後に UpdatingBatch インスタンスをダンプするための行をメソッド createAndFileDocument の終わり付近に追加します。UpdatingBatch 内のオブジェクトの最新表示を要求しましたが、文書または RCR に対して PropertyFilter を指定しなかったので、この最新表示では、これらのオブジェクトに定義されたプロパティーが多数戻されます。ただし、プロパティーの大部分は、評価対象外または参照です。
リスト 7. ダンプ呼び出しを含む変更済みコード
private String createAndFileDocument(Domain dom, ObjectStore os)
{
// . . .
UpdatingBatch ub=UpdatingBatch.createUpdatingBatchInstance(dom,RefreshMode.REFRESH);
ub.add(doc, null);
ub.add(rcr, null);
ObjectDumper od = new ObjectDumper();
try
{
od.dump(ub);
}
catch (IOException e)
{
e.printStackTrace();
System.exit(0);
}
System.out.println("Doing updates via UpdatingBatch");
ub.updateBatch();
try
{
od.clear();
od.dump(ub);
}
catch (IOException e)
{
e.printStackTrace();
System.exit(0);
}
// . . .
}
|
リスト 8 は、最新表示前の UpdatingBatch インスタンスのダンプを示しています。例によって、長い行は円記号で意図的に改行しています。3 つのドットは、ダンプの省略部分を示しています。オブジェクトに含まれるプロパティーは比較的少なく、これらはすべてダーティーであることが分かります。言い換えれば、呼び出し側アプリケーションによって設定された値を持っているということです。ObjectDumper は、ダーティーなプロパティーの名前をアスタリスクでマークします。保留中のアクションも存在しており、これらは、UpdatingBatch のオブジェクトに適しています。
リスト 8. 最新表示前の UpdatingBatch
[0118cb3a] UpdatingBatch
[00c67a88] ArrayList
[0096ad7c] BatchItemHandleImpl
[0057ae58] DocumentImpl classId=Document&\
objectId={7091F9E9-4982-43BA-88B0-9DFAA80C356D}&\
objectStore=MyObjectStore
[00775121] PendingAction[2]
[016f70a4] Create Class=com.filenet.api.action.Create \
Values={classId=Document, objectId={7091F9E9-4982-43BA-88B0-9DFAA80C356D}}
[014c5b37] Checkin Class=com.filenet.api.action.Checkin \
Values={checkinminorversion=false, autoclassify=false}
[01f8acdc] PropertiesImpl n=2, dirty=true
[011cc367] PropertyStringImpl *DocumentTitle=My Document Title
[0160c4b0] PropertyEngineObjectListImpl *ContentElements=((collection))
[00114025] SubListImpl
[013c550f] ContentTransferImpl \
Class=com.filenet.apiimpl.core.ContentTransferImpl \
AccessAllowed=null RecursionLevel=0 UpdateSequenceNumber=null \
ObjectAddress=( com.filenet.apiimpl.core.DependentIdentity@a347a9da \
Parent=(null) Index=null PropertyName=null IsNew=true) Connection=(null) \
SuperClasses=[null] PendingActions=null
[01f488f1] PropertiesImpl n=3, dirty=true
[014a9387] PropertyStringImpl \
*RetrievalName=c:/temp/qbf.txt1
[011b86c7] PropertyContentImpl \
*Content=com.filenet.apiimpl.property.ClientInputStream@2da5a6
[00d647d8] PropertyStringImpl \
*ContentType=application/octet-stream
[002a987d] BatchItemHandleImpl
[00813bc1] DynamicReferentialContainmentRelationshipImpl \
classId=DynamicReferentialContainmentRelationship&\
objectId={F335B56F-FAEA-42D1-9946-E08565112767}&\
objectStore=MyObjectStore
[007a36a2] PendingAction[1]
[0198c6f3] Create Class=com.filenet.api.action.Create \
Values={defineSecurityParentage=false, autouniquecontainmentname=true, \
classId=DynamicReferentialContainmentRelationship, \
objectId={F335B56F-FAEA-42D1-9946-E08565112767}}
[012d8ecd] PropertiesImpl n=3, dirty=true
[01fa5e5e] PropertyStringImpl *ContainmentName=I Am a Contained Document
[00497062] PropertyEngineObjectImpl *Tail=classId=Folder&\
path=/HelloDocument&objectStore=MyObjectStore
[01716fa0] PropertyEngineObjectImpl *Head=classId=Document&\
objectId={7091F9E9-4982-43BA-88B0-9DFAA80C356D}&objectStore=MyObjectStore
|
リスト 9 は、最新表示後の UpdatingBatch インスタンスのダンプを示しています。保留中のアクションは、クリアされています。戻される多くのプロパティー (文書の場合は 66、RCR の場合は 13) は、CE サーバーから提供されているので、ダーティーではありません。多くのプロパティーは、評価対象外または参照です。説明のため、これらのいくつかをリスト 9 に示します。ただし、大部分のダンプ行は、簡略化と分かりやすくするために短縮されています。
リスト 9. 最新表示後の UpdatingBatch
[0118cb3a] UpdatingBatch
[0181b3d4] ArrayList
[0096ad7c] BatchItemHandleImpl
[0057ae58] DocumentImpl classId=Document&\
objectId={7091F9E9-4982-43BA-88B0-9DFAA80C356D}&\
objectStore={5463CB9C-C05C-4ED6-8DB2-EA931DC026F8}
[0045378f] PendingAction[0]
[014b9a74] PropertiesImpl n=66, dirty=false
[00893969] PropertyEngineObjectImpl ReleasedVersion=((UNEVALUATED)) \
Class=com.filenet.apiimpl.property.PropertyEngineObjectImpl \
PropertyName=ReleasedVersion Value=classId=Document&\
objectId={7091F9E9-4982-43BA-88B0-9DFAA80C356D}&\
objectStore={5463CB9C-C05C-4ED6-8DB2-EA931DC026F8}&propertyId=ReleasedVersion \
IsDirty=false Access=1 State=(UNEVALUATED) Connection=( \
Class=com.filenet.apiimpl.core.ConnectionImpl \
URI=http://MyCEServer:9080/wsi/FNCEWS40MTOM/ Parameters={})
[014b081b] PropertyEngineObjectImpl VersionSeries=((REFERENCE)) \
Class=com.filenet.apiimpl.property.PropertyEngineObjectImpl \
PropertyName=VersionSeries Value=classId=VersionSeries&\
objectId={642B77E7-EE55-4E96-8C9E-4C005D800361}&\
objectStore={5463CB9C-C05C-4ED6-8DB2-EA931DC026F8} \
IsDirty=false Access=1 State=(REFERENCE) Connection=( \
Class=com.filenet.apiimpl.core.ConnectionImpl \
URI=http://MyCEServer:9080/wsi/FNCEWS40MTOM/ Parameters={})
[00e8606c] PropertyEngineObjectListImpl Permissions=((UNEVALUATED)) . . .
[00292cb2] PropertyDateTimeImpl DateLastModified=Sun Jan 25 21:13:56 PST 2009
[0135605a] PropertyStringImpl EntryTemplateLaunchedWorkflowNumber=null
[0148e798] PropertyBooleanImpl IsCurrentVersion=true
[015ccfb1] PropertyEngineObjectImpl OwnerDocument=null
[004788d5] PropertyEngineObjectImpl DocumentLifecyclePolicy=null
[00688954] PropertyStringImpl LockOwner=null
[0110278e] PropertyEngineObjectImpl SecurityPolicy=null
[0194e776] PropertyStringImpl Name=My Document Title
[00e80740] PropertyEngineObjectImpl StorageArea=((REFERENCE)) . . .
[00f2225f] PropertyEngineObjectSetImpl Annotations=((UNEVALUATED)) . . .
[006de609] PropertyEngineObjectSetImpl \
ParentRelationships=((UNEVALUATED)) . . .
[01f217ec] PropertyStringImpl DocumentTitle=My Document Title
[0100aff5] PropertyBinaryImpl PublicationInfo=null
[00200db9] PropertyEngineObjectImpl SecurityParent=((UNEVALUATED)) . . .
[015dc37d] PropertyEngineObjectImpl StoragePolicy=((REFERENCE)) . . .
[00e7e8eb] PropertyEngineObjectImpl PublishingSubsidiaryFolder=null
[016cbd97] PropertyStringImpl CurrentState=null
[001321f5] PropertyInteger32Impl ClassificationStatus=0
[001a6518] PropertyEngineObjectImpl ClassDescription=((REFERENCE)) . . .
[013e4a5a] PropertyEngineObjectSetImpl \
WorkflowSubscriptions=((UNEVALUATED)) . . .
[019050a0] PropertyStringImpl StorageLocation=null
[019d3b3a] PropertyFloat64Impl ContentSize=110.0
[019b808a] PropertyDateTimeImpl DateContentLastAccessed=null
[00140fee] PropertyEngineObjectSetImpl ParentDocuments=((UNEVALUATED)) . . .
[0082254d] PropertyDateTimeImpl DateCreated=Sun Jan 25 21:13:56 PST 2009
[005f1ae9] PropertyInteger32Impl ReservationType=32
[01dfc8a0] PropertyStringImpl EntryTemplateObjectStoreName=null
[00ec898a] PropertyEngineObjectSetImpl AuditedEvents=((UNEVALUATED)) . . .
[015e0c2b] PropertyInteger32Impl VersionStatus=1
[0171194d] PropertyBooleanImpl IsInExceptionState=false
[0034151f] PropertyEngineObjectListImpl ActiveMarkings=((UNEVALUATED)) . . .
[00114b17] PropertyStringImpl MimeType=application/octet-stream
[0159054d] PropertyStringImpl LastModifier=user1234
[016b321b] PropertyEngineObjectListImpl ContentElements=((UNEVALUATED)) . . .
[0098f192] PropertyEngineObjectImpl Reservation=((UNEVALUATED)) . . .
[017748d3] PropertyStringImpl Creator=user1234
[004e2f0a] PropertyEngineObjectSetImpl FoldersFiledIn=((UNEVALUATED)) . . .
[00c2cf83] PropertyBooleanImpl IsFrozenVersion=false
[01c5af2e] PropertyStringImpl ComponentBindingLabel=null
[01702c48] PropertyInteger32Impl LockTimeout=null
[016b6c55] PropertyInteger32Impl MajorVersionNumber=1
[01954f89] PropertyIdImpl Id={7091F9E9-4982-43BA-88B0-9DFAA80C356D}
[0198e8b4] PropertyEngineObjectSetImpl \
DependentDocuments=((UNEVALUATED)) . . .
[004b0bbb] PropertyStringListImpl ContentElementsPresent=((collection))
[00ef4504] StringListImpl
[002f1e75] String application/octet-stream
[01c5ddd3] PropertyEngineObjectImpl This=((REFERENCE)) . . .
[01f47ae8] PropertyInteger32Impl MinorVersionNumber=0
[01b11b79] PropertyEngineObjectImpl SourceDocument=null
[0082d603] PropertyDateTimeImpl ContentRetentionDate=null
[01b09282] PropertyBooleanImpl IsReserved=false
[0162ba99] PropertyIdImpl LockToken=null
[00c8c7d6] PropertyIdImpl EntryTemplateId=null
[01b7b407] PropertyInteger32Impl CompoundDocumentState=0
[018c5e67] PropertyBooleanImpl IsVersioningEnabled=true
[0089c116] PropertyBooleanImpl IgnoreRedirect=null
[01e3bbd7] PropertyEngineObjectSetImpl Versions=((UNEVALUATED)) . . .
[00a86d12] PropertyEngineObjectImpl SecurityFolder=null
[0190d8e1] PropertyStringImpl Owner=user1234@example.net
[008eae04] PropertyEngineObjectSetImpl Containers=((UNEVALUATED)) . . .
[0135ae7e] PropertyEngineObjectSetImpl \
DestinationDocuments=((UNEVALUATED)) . . .
[009abce9] PropertyEngineObjectImpl CurrentVersion=((UNEVALUATED)) . . .
[0095215b] PropertyIdImpl IndexationId=null
[00c10de0] PropertyEngineObjectSetImpl \
ChildRelationships=((UNEVALUATED)) . . .
[0056fc16] PropertyEngineObjectSetImpl ChildDocuments=((UNEVALUATED)) . . .
[002a987d] BatchItemHandleImpl
[00813bc1] DynamicReferentialContainmentRelationshipImpl \
classId=DynamicReferentialContainmentRelationship&\
objectId={F335B56F-FAEA-42D1-9946-E08565112767}&\
objectStore={5463CB9C-C05C-4ED6-8DB2-EA931DC026F8}
[018f9b75] PendingAction[0]
[01c85444] PropertiesImpl n=13, dirty=false
[01144ba2] PropertyEngineObjectImpl VersionSeries=((REFERENCE)) . . .
[008de972] PropertyStringImpl Creator=user1234
[00d964af] PropertyDateTimeImpl DateLastModified=Sun Jan 25 21:13:56 PST 2009
[006127da] PropertyStringImpl ContainmentName=I Am a Contained Document
[007f8062] PropertyEngineObjectImpl ClassDescription=((REFERENCE)) . . .
[01f8d077] PropertyEngineObjectImpl Tail=((REFERENCE)) . . .
[00ee6ad6] PropertyIdImpl Id={F335B56F-FAEA-42D1-9946-E08565112767}
[00a826da] PropertyEngineObjectImpl This=((REFERENCE)) . . .
[015fc672] PropertyEngineObjectImpl Head=((REFERENCE)) . . .
[0178aae1] PropertyDateTimeImpl DateCreated=Sun Jan 25 21:13:56 PST 2009
[011abd68] PropertyStringImpl Name=I Am a Contained Document
[008932e8] PropertyEngineObjectSetImpl AuditedEvents=((UNEVALUATED)) . . .
[00c08593] PropertyStringImpl LastModifier=user1234
|
利点
- ObjectDumperを使用すると、任意の時点のオブジェクトの状態をアプリケーションで表示できます。RPC の機能に制限されません。
- ダンプされた情報およびその情報の形式の両方に適合するようにカスタマイズできます。これを行うには、ObjectDumper のソース・コードをサブクラス化するか、直接変更します。
- ソース・コードがあるので、その動作を完全に制御できます。ご自分で考案したものを除き、習得すべき新しいツールまたは構成メカニズムはありません。
欠点
- 通常は、装備されたアプリケーションのソース・コードを、ObjectDumper を使用するように変更する必要があります。ソース・コードを使用できないコンポーネントを装備する場合は、あまり有用でありません。
- ダンプ呼び出しをコンポーネントに追加するときは、それらのコンポーネントを再構築する必要があります。そのため、たとえソース・コードがあっても (一般に、通常 ObjectDumper を使用するようなケース)、アプリケーションを再構築して再デプロイするときに小さな混乱が起こる場合があります。
この記事では、RPC で渡されるデータ、および P8 Content Engine Java API オブジェクト全般の状態をモニターする 3 つの手法を個々に説明しました。これらの手法の詳細をすべては説明しませんでしたが、それぞれの特徴は十分に示したので、後はご自分でさらに調べることができるはずです。それぞれの手法には長所と短所がありますが、どの手法をいつ使用するかを考える必要があります。将来の記事では、特にパフォーマンス・チューニングの説明の中で、これらの手法に再度言及したいと思います。
| 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|---|---|---|
| この記事の Java ソース・コード | ObjectDumper.zip | 6KB | HTTP |
学ぶために
- このシリーズの次回の記事に関する通知を要求するには、RSS フィードを使用してください。(developerWorks コンテンツの RSS フィードについて詳しく調べてください。)
- さらに詳しく学ぶには、このシリーズの他の記事を参照してください。
- IBM FileNet 製品に関連する いくつかの IBM Redbooks® を探してください。CE と PE の API についてこの連載よりもさらに詳しく学習できます。この製品群に関する理解を深めるための貴重な資料です。「IBM FileNet Content Manager Implementation Best Practices and Recommendations」および「IBM FileNet Business Process Manager 入門」から始めてください。関連する IBM Redbooks をさらに探すには、サイトで filenet という用語を検索してください。
- IBM developerWorks には、多数の著者による IBM FileNet 製品関連のさまざまな記事が掲載されています。このコンテンツを探すには、IBM developerWorks サイトで filenet という用語を検索してください。
- 各種の IBM ECM 製品 (IBM FileNet Content Manager を含む) の API のコーディング手法を比較するには、チュートリアル「Develop applications using the IBM Enterprise Content Management Java APIs with IBM Rational Application Developer」を参照してください。
- P8 プラットフォームと J2EE の統合方法の主な側面のハイライトについては、記事「Upgrade from FileNet P8 3.5 to 4.0: An architectural shift」を参照してください。P8 と J2EE のアーキテクチャーの統合を理解することは、カスタム・ソリューションの開発方法を理解する鍵となります。
- IBM FileNet BPM のカスタム・コンポーネントの開発プロセスのヒントおよび近道については、記事シリーズ「Develop FileNet P8 BPM 4.0 custom components using Eclipse」を参照してください。
- IBM FileNet Business Process Framework (BPF) を使用して非常に一般的なタイプのソリューションを作成する方法の具体的な説明については、記事シリーズ「Build BPM applications using FileNet」を参照してください。
- Internet Assigned Numbers Authority (IANA) が保守している公式のポート番号レジストリーを参照してください。
- HTTP 1.1 仕様である RFC-2616 を参照してください。
- Apache Software Foundation プロジェクトを探してください。
- developerWorks の Information Management ゾーン : 情報管理の詳細を参照してください。技術資料、how-to 記事、セミナー、ダウンロード、製品情報などを見つけることができます。
- developerWorks の技術イベントや Web キャストに関する最新情報を得ることができます。
製品や技術を入手するために
- 最初に Enterprise Content Management からの IBM ECM オファリングに関する情報を入手してください。「Enterprise Content Managementのページ(US) のLibrary」のセクションでは、多数のホワイト・ペーパー、製品概要、アナリスト・レポート、およびその他の製品情報があります。API とその他の側面に関する詳細な P8 プラットフォーム資料が「ECM FN01 オンラインマニュアル」から入手できます。
- developerWorks から直接ダウンロードできる IBM 試用版ソフトウェアで次の開発プロジェクトを作成してください。
議論するために
- 各種の ECM-関連の developerWorks フォーラム (IBM FileNet 製品に特に関連するいくつかのサブフォーラムを含む) に参加してください。特に、IBM FileNet Content Manager フォーラムと FileNet Business Process Manager フォーラムを確認してください。これらのフォーラムは主にコミュニティー主導型であり、公式の製品サポート機構の代わりとなるものではありません。
- developerWorks のブログや developerWorks コミュニティーに参加することができます。

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