C でのメッセージ処理ノードの作成

始める前に

ロード可能インプリメンテーション・ライブラリー、または LIL、 は C ノード (またはパーサー) のインプリメンテーション・モジュールです。 LIL はダイナミック・リンク・ライブラリー (DLL) としてインプリメントされます。 これにはファイル拡張子 .dll は付けられず、.lil が付けられます。

開発者が作成する必要のあるインプリメンテーション関数は、C 言語ノード・インプリメンテーション関数でリストしています。このプロセスに役立てるために WebSphere Business Integration Message Broker によって提供されるユーティリティー関数は、C 言語ノード・ユーティリティー関数でリストしています。

WebSphere Business Integration Message Broker では、SwitchNode および TransformNode という名前の 2 つのサンプル・ユーザー定義ノードのソースが準備されています。 これらのノードは現行の状態で使用することもできますし、変更を加えてもかまいません。

概念上、メッセージ処理ノードは何らかの形でメッセージを処理するために使用され、 出力ノードはメッセージをビット・ストリームとして出力するために使用されます。 しかし、メッセージ処理ノードや出力ノードをコーディングするとき、 これらは基本的に同じものです。 出力ノードでメッセージ処理を実行することもできますし、 同様に、メッセージ処理ノードを使用してメッセージをビット・ストリームとして出力することもできます。 単純化するため、 このトピックではメッセージ処理ノードとしてノードを主に参照していますが、 どちらのタイプのノードの機能についても取り上げます。

メッセージ・データへのアクセス

多くの場合、ユーザー定義ノードは、 その入力ターミナルで受け取ったメッセージの内容にアクセスする必要があります。 メッセージは、構文エレメントのツリーとして表されます。 ユーティリティー関数のグループが、メッセージ管理、メッセージ・バッファー・アクセス、 構文エレメント・ナビゲーション、および構文エレメント・アクセス用に提供されます。 (ユーティリティー関数の詳細については、C 言語ノード・ユーティリティー関数を参照してください。)

実行する必要があるかもしれない照会のタイプには、次のようなものがあります。
  • 必要なメッセージ・オブジェクトのルート・エレメントを取得する。
  • 名前によって子エレメントや兄弟エレメントを照会することによって、ツリーをナビゲートまたは照会する。
  • エレメントのタイプを取得する。
  • エレメントの値を取得する。

たとえば、本体の最初の子の名前とタイプを照会するには、次のようにします。

void cniEvaluate( ...
){                                    
  ...
/* Navigate to the target element */
    rootElement = cniRootElement(&rc, message);
    bodyElement = cniLastChild(&rc, rootElement);
    bodyFirstChild = cniFirstChild(&rc, bodyElement);

/* Query the name and value of the target element */
    cniElementName(&rc, bodyFirstChild, (CciChar*)&elementname, sizeof(elementName));
    bytes = cniElementCharacterValue(
		&rc, bodyfirstChild, (CciChar*)&eValue, sizeof(eValue));
  ...    
}

メッセージ・オブジェクトの変換

受信した入力メッセージは読み取り専用です。 したがって、メッセージを変換するには、まず cniCreateMessage 関数を使用して、それを新しい出力メッセージに書き込む必要があります。 入力メッセージからエレメントをコピーするか、 あるいは、新しいエレメントを作成してそれらをメッセージに付加することができます。 新しいエレメントは通常はパーサーのドメインに入ります。

たとえば、次のようにします。
  1. 着信メッセージを新しいメッセージに書き込むには、次のようにします。
    {
      ...
        context = cniGetMessageContext(&rc, message));
        outMsg = cniCreateMessage(&rc, context));
      ...
    }
  2. ターゲット・エレメントの値を変更するには、次のようにします。
      cniSetElementIntegerValue(&rc, targetElement, L"newValue", 8);
  3. メッセージをファイナライズして伝搬したら、 次のように、cniDeleteMessage 関数を使用して、出力メッセージを削除する必要があります。
      cniDeleteMessage(&rc, outMsg);

ESQL へのアクセス

ノードは、Compute ノードの ESQL 構文を使用して ESQL 式を呼び出すことができます。 ユーザーは、ESQL 式を使用してメッセージのコンポーネントを作成および変更し、 cniSqlCreateStatementcniSqlSelectcniSqlDeleteStatement 、および cniSqlExecute 関数を使用して、 入力メッセージと外部データベースのデータの両方を参照できます。

たとえば、 データベース・テーブル内の列の内容から Result エレメントを取り込むには、 次のようにします。

{
  ...
    sqlExpr = cniSqlCreateStatement(&rc,
      (NODE_CONTEXT_ST *)context->nodeObject,
      L"DB", CCI_SQL_TRANSACTION_AUTO,
      L"SET OutputRoot.XML.Result[] = (SELECT T.C1 AS Col1 FROM Database.TABLE AS T;");
  ...
    cniSqlSelect(&rc, sqlExpr, destinationList, exceptionList, message, outMsg);
    cniSqlDeleteStatement(&rc, sqlExpr);
  ...                                                               
}

ESQL の詳細については、ESQLを参照してください。

メッセージの伝搬

メッセージを伝搬する前に、何のメッセージ・フロー・データを伝搬するか、 また、どのターミナルでデータを受信するかを決定する必要があります。
  1. メッセージが変更されている場合は、 そのメッセージを伝搬する前に、cniFinalize 関数を使用してファイナライズする必要があります。 たとえば、
          cniFinalize(&rc, outMsg, CCI_FINALIZE_NONE);
  2. terminalObject は、ユーザー定義ノード自体が保守しているリストに由来します。 出力ターミナルにメッセージを伝搬するため、 次のように、cniPropagate 関数を使用します。
      if (terminalObject) {
            if (cniIsTerminalAttached(&rc, terminalObject)) {
                if (rc == CCI_SUCCESS) {
                    cniPropagate(&rc, terminalObject, destinationList, exceptionList, outMsg);
          }
        }
  3. cniCreateMessage を使用して新しい出力メッセージを作成した場合は、 メッセージを伝搬した後で、次のようにして cniDeleteMessage 関数を使用して出力メッセージを削除する必要があります。
      cniDeleteMessage(&rc, outMsg);

出力装置への書き込み

変換されたメッセージは、直列化してビット・ストリームにする必要があります。 その後、そのビット・ストリームにアクセスして、それを出力装置に書き込むことができます。 メッセージは、 cniWriteBuffer 関数を使用してビット・ストリームに書き込みます。 たとえば、
{
  ...
    cniWriteBuffer(&rc, message);
    writeToDevice(cniBufferPointer(&rc, message), cniBufferSize(&rc, message));
  ...                                                               
}
メッセージは一度だけ直列化できます。
注: WebSphere MQ キューに書き込むときは、提供されている MQOutput ノードを使用しなければなりません。 これは、ブローカーが、スレッド単位で、内部的に WebSphere MQ 接続およびオープン・キュー・ハンドルを保守し、 それらはパフォーマンスを最適化するためにキャッシュに入れられるからです。 さらに、ブローカーは特定の WebSphere MQ イベントの発生時に リカバリー・シナリオを処理しますが、 WebSphere MQ MQI 呼び出しが ユーザー定義出力ノードで使用された場合、これは不利な影響を受けます。

ブローカーへのノードの宣言

以下の手順は、ノードをブローカーへ宣言する方法を表しています。

  1. LIL がロードされ、オペレーティング・システムによって初期化されると、 ブローカーによって初期化関数 bipGetMessageflowNodeFactory が呼び出されます。 これはブローカーの構成スレッドから呼び出されます。ブローカーは、 LIL が実行できる事柄と、ブローカーが LIL を呼び出す方法を理解するために、この関数を呼び出します。 たとえば、以下のようになります。
    CciFactory LilFactoryExportPrefix * LilFactoryExportSuffix
    bipGetMessageflowNodeFactory()
  2. bipGetMessageflowNodeFactory 関数が、 ユーティリティー関数 cniCreateNodeFactory を呼び出します。 この関数は、LIL がサポートするすべてのノードのファクトリー名 (またはグループ名) を戻します。 たとえば、以下のようになります。
    {
    	CciFactory* factoryObject;
    	int rc = 0;
    	CciChar factoryName[] = L"SwitchNodeFactory";
    	CCI_EXCEPTION_ST exception_st;
    
    	/* Create the Node Factory for this node */
    	factoryObject = cniCreateNodeFactory(0, factoryName);
    	if (factoryObject == CCI_NULL_ADDR) {
    		if (rc == CCI_EXCEPTION) {
    			/* Get details of the exception */
    			cciGetLastExceptionData(&rc, &exception_st);
    
    			/* Any local error handling can go here */
    
    			/* Rethrow the exception */
    			cciRethrowLastException(&rc);
    		}
    
    		/* Any further local error handling can go here */
    	}
    	else {
    		/* Define the nodes supported by this factory */
    		defineSwitchNode(factoryObject);
    	}
    
    	/* Return address of this factory object to the broker */
    	return(factoryObject);
    }

メッセージ処理ノードとしてのノードの定義

LIL は次にユーティリティー関数 cniDefineNodeClass を呼び出して、 各ノードの名前と、インプリメンテーション関数のアドレスの仮想関数テーブルを渡します。 たとえば、 SwitchNode という名前の単一のノードとその関数テーブルを定義するには、 次のようにします。
void defineSwitchNode(void* factoryObject){
	static CNI_VFT vftable = {CNI_VFT_DEFAULT};

	/* Setup function table with pointers to node implementation functions */
	vftable.iFpCreateNodeContext = _createNodeContext;
	vftable.iFpDeleteNodeContext = _deleteNodeContext;
	vftable.iFpGetAttributeName = _getAttributeName;
	vftable.iFpSetAttribute = _setAttribute;
	vftable.iFpGetAttribute = _getAttribute;
	vftable.iFpEvaluate = _evaluate;

	cniDefineNodeClass(0, factoryObject, L"SwitchNode", &vftable);

	return;
}
これは構成スレッドから呼び出されます。

ユーザー定義ノードは、cniEvaluate 関数をインプリメントすることによって、 入力ノードの機能を提供するという役割を果たします。 ユーザー定義ノードは、 cniEvaluate インプリメンテーションか cniRun インプリメンテーションのいずれか、 または両方をインプリメントしていなければなりません。 そうでないと、ブローカーはそのユーザー定義ノードをロードせず、 cniDefineNodeClass ユーティリティー関数が失敗して、 CCI_MISSING_IMPL_FUNCTION が戻されます。

ユーザー定義メッセージ処理ノードが含まれたメッセージ・フローが正常にデプロイされていれば、 メッセージ・フローに渡される各メッセージごとに、 そのノードの cniEvaluate 関数が呼び出されます。

メッセージ・フロー・データがノードの入力ターミナルで受信されます。 メッセージ・フロー・データとは、メッセージ、グローバル環境、ローカル環境、および例外リストです。

たとえば、
void cniEvaluate(
    CciContext* context,
    CciMessage* destinationList,
    CciMessage* exceptionList,
    CciMessage* message
){                                    
  ...
}

ノードのインスタンスの作成

以下の手順は、ノードをインスタンス化する方法を表しています。

  1. ブローカーは、関数ポインターのテーブルを受信すると、 ユーザー定義ノードの各インスタンス化ごとに関数 cniCreateNodeContext を呼び出します。 ユーザー定義ノードを使用する 3 つのメッセージ・フローがある場合は、 3 つのそれぞれに対して cniCreateNodeContext 関数が呼び出されます。 この関数は、そのユーザー定義ノードのインスタンス化用に、 構成された属性の値を保持するためのメモリーを割り振ります。 たとえば、以下のようになります。
    1. ユーザー関数 cniCreateNodeContext が呼び出されます。
      CciContext* _Switch_createNodeContext(
          CciFactory* factoryObject,
          CciChar*    nodeName,
          CciNode*    nodeObject
      ){
          static char* functionName = (char *)"_Switch_createNodeContext()";
          NODE_CONTEXT_ST* p;
          CciChar          buffer[256];
      
      
    2. ローカル・コンテキストにポインターを割り振り、コンテキスト・エリアを消去します。
            p = (NODE_CONTEXT_ST *)malloc(sizeof(NODE_CONTEXT_ST));
      
          if (p) {
                memset(p, 0, sizeof(NODE_CONTEXT_ST));
    3. コンテキストの中のノード・オブジェクト・ポインターを保存します。
         p->nodeObject = nodeObject;
    4. ノード名を保存します。
        CciCharNCpy((CciChar*)&p->nodeName, nodeName, MAX_NODE_NAME_LEN);
  2. ブローカーが適切なユーティリティー関数を呼び出して、 ノードの入力ターミナルと出力ターミナルについての情報を検出します。 ノードには、 いくつかの入力ターミナルと出力ターミナルが関連付けられています。 ユーザー関数 cniCreateNodeContext 内で、 cniCreateInputTerminal および cniCreateOutputTerminal への呼び出しがなされ、 ユーザー・ノードのターミナルが定義されます。 これらの関数は、cniCreateNodeContext インプリメンテーション関数内で呼び出されなければなりません。 たとえば、1 つの入力ターミナルと 2 つの出力ターミナルを持つノードを定義するには、 次のようにします。
        {
                const CciChar* ucsIn = CciString("in", BIP_DEF_COMP_CCSID) ;
                insInputTerminalListEntry(p, (CciChar*)ucsIn);
                free((void *)ucsIn) ;
        }
        {
                const CciChar* ucsOut = CciString("out", BIP_DEF_COMP_CCSID) ;
                insOutputTerminalListEntry(p, (CciChar*)ucsOut);
                free((void *)ucsOut) ;
        }
        {
                const CciChar* ucsFailure = CciString("failure", BIP_DEF_COMP_CCSID) ;
                insOutputTerminalListEntry(p, (CciChar*)ucsFailure);
                free((void *)ucsFailure) ;
        }

属性の設定

属性は、ブローカーを開始するとき、 あるいは新しい値を持つメッセージ・フローを再デプロイするときに設定します。 構成スレッド上のユーザー・コードを呼び出すブローカーによって、属性が設定されます。 そのユーザー・コードでは、後にメッセージを処理するときに使用するために、 そのノードのコンテキスト・エリアにこれらの属性を保管する必要があります。

入力ターミナルと出力ターミナルの作成後、 ブローカーは cniSetAttribute 関数を呼び出して、 このユーザー定義ノードのインスタンス化用に構成されている属性の値を渡します。 たとえば、
    {
            const CciChar* ucsAttr = CciString("nodeTraceSetting", BIP_DEF_COMP_CCSID) ;
            insAttrTblEntry(p, (CciChar*)ucsAttr, CNI_TYPE_INTEGER);
            _setAttribute(p, (CciChar*)ucsAttr, (CciChar*)constZero);
            free((void *)ucsAttr) ;
    }
    {
            const CciChar* ucsAttr = CciString("nodeTraceOutfile", BIP_DEF_COMP_CCSID) ;
            insAttrTblEntry(p, (CciChar*)ucsAttr, CNI_TYPE_STRING);
            _setAttribute(p, (CciChar*)ucsAttr, (CciChar*)constSwitchTraceLocation);
            free((void *)ucsAttr) ;
    }

ノード機能のインプリメント

ブローカーがキューからメッセージを取り出し、 そのメッセージがユーザー定義のメッセージ処理ノードまたは出力ノードの入力ターミナルに到着すると、 ブローカーはインプリメンテーション関数 cniEvaluate を呼び出します。 この関数はメッセージ処理スレッド上に呼び出され、メッセージを使用して何を行うかを決定します。 特に追加のインスタンスが使用される場合に、この関数はマルチスレッド上に呼び出される場合もあります。

メッセージ処理ノードのインスタンスの削除

ノードのインスタンスを削除するには、 cniDeleteNodeContext 関数を使用します。 たとえば、以下のようになります。

void _deleteNodeContext(
    CciContext* context
){
    static char* functionName = (char *)"_deleteNodeContext()";

    return;
}

cniDeleteNodeContext 関数はユーザーによって 提供され、メッセージ・フローが削除されたときにブローカーによって呼び出されます。

関連概念
ESQL
ユーザー定義拡張機能の計画
ランタイム環境でのユーザー定義拡張機能
ユーザー定義拡張機能の設計
ユーザー定義の入力ノード

関連タスク
ユーザー定義拡張機能の開発
提供されているサンプルのインプリメント
C ユーザー定義拡張機能のコンパイル

関連資料
C ユーザー定義ノード API