2010年3月21日日曜日

Oracle BPEL/string-join

入力メッセージ中の繰り返し要素を、区切り文字で連結して他のサービスに渡したりするようなコーディングは良くあると思う。例えば、検索用の Webサービスが外部にあるとして、これに渡す条件文を組み立てたりするのが典型的か。

この部分を 敢えてBPEL だけで書いたらこんな風になるけど...
...実際にはそんなフローを書く人は少ない。BPEL やワークフローを体系的に勉強していなくても、こんな粒度の細々したステップを書くのはまずかろうと、いくらなんでも普通は気づく(IDE は JDeveloper)。

で XSLT で書いたりするが、惜しいかな、いかにも手続き型っぽいこんな感じのコードが多い。
<xsl:template match="/">
<client:processResponse>
<client:result>
<xsl:for-each select="/client:process/client:input">
<xsl:choose>
<xsl:when test="position() > 1">
<xsl:text>,</xsl:text>
<xsl:value-of select="."/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</client:result>
</client:processResponse>
</xsl:template>
JDeveloper の XSLマップ デザイナ前提だと手続き型になるしかないけど、上記コードは実はこの時点でデザイナが使えなくなってる。<for-each> の使い方が違うとか、<choose> 内には<when> 要素が必要だとか意味不明なことを言ってくるが、見ての通りそんな問題は存在しない。

というわけで、これ以上糞ツールに気を使っても面白くないので、XSLT らしく関数型で書いてみる。
<xsl:template match="/">
<client:processResponse>
<client:result>
<xsl:apply-templates select="client:process/client:input[1]"/>
</client:result>
</client:processResponse>
</xsl:template>

<xsl:template match="client:input[following-sibling::client:input]">
<xsl:value-of select="."/>
<xsl:text>-</xsl:text>
<xsl:apply-templates select="following-sibling::client:input[1]"/>
</xsl:template>

<xsl:template match="client:input[not(following-sibling::client:input)]">
<xsl:value-of select="."/>
</xsl:template>
繰り返し文や条件文を全否定する気はサラサラないけど、関数型の書法の方が応用が利くので、こっちの方がお勧め。

もう一個の改善点は、XSLT 変換の範囲。

上記コードは、もともと Transform アクティビティ生成の結果作られたものを書き直したものなので、BPEL側の<assign>ではメッセージタイプからメッセージタイプへの変換になっている。従って、client:processResponse がもっと複雑な構造だと、<xsl:template match="/"> の中に 要素の複製や空要素の生成など無駄なコードが大量発生がちなので、次はこれを改善。
<xsl:template match="/">
<xsl:apply-templates select="client:process/client:input[1]"/>
</xsl:template>

<xsl:template match="client:input[following-sibling::client:input]">
以下同文

上記コードの出力は連結したテキストのみとなるので、このXSLTの出力を受ける BPEL 内の<to>要素に query 属性で processResponse/client:result を指定しておけば、最小限の要素をピンポイントで指定したコードになる。(XSLT内に<xsl:output method="text"/>も含めておく)

実は、更にもう一段階ある。

どうも JDeveloperでは、標準的な XSLT 関数なのに IDE的にサポートされていないものがあるらしい。例えば document()関数なども、ドロップダウンリストに表示されなかったりする。

ここまで例として取り上げてきた文字列の連結では、string-join 関数が使える。せっかく再帰で書いた上の XSLTコードがもったいないけど、実は 3行で書けちゃったりする。
<xsl:template match="/client:process">
<xsl:value-of select="string-join(client:input, ',')"/>
</xsl:template>

うーん、なんだか今日はずいぶん無駄なコードを書いたかも・・・

2010年3月19日金曜日

雑感:BPELとXSLT

なんかのフレームワークの設定ファイルくらいでしか XML を触った事のない Java/.NET の技術者が、いきなり BPEL 書きの仕事を始めると、SOAP や WSDL のような SOA/Webサービスの仕様だとか、それ以前の XSLT や XPath のような XML の基礎技術まで、BPEL というカテゴリに包含される技術だと思ってしまう傾向があるらしい。
特に XSLT などは、Transform アクティビティ(Oracle BPELの話)でしか使った事がない人が想像するよりも、本来は遥かにいろいろできたりするので、けっこうもったいない。

例として、1 から任意の自然数nまでの合計を求める処理を考えてみる。
これを 変数とループを使って手続き型のBPEL コードで書くこともできるけど、初歩のXSLT を知っていれば以下のようなXSLT で書くことができる。
<?xml version="1.0" encoding="windows-31j" ?>

<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:n1="http://xmlns.oracle.com/OracleBpelTest1App_jws/Project2/BPELProcess2">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:call-template name="sum">
<xsl:with-param name="n" select="n1:process/n1:n" />
</xsl:call-template>
</xsl:template>
<xsl:template name="sum">
<xsl:param name="n"/>
<xsl:choose>
<xsl:when test="$n = 1">1</xsl:when>
<xsl:otherwise>
<xsl:variable name="x">
<xsl:call-template name="sum">
<xsl:with-param name="n" select="$n - 1" />
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="$n + $x" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

これを "exercise1.xsl" というファイル名でプロジェクトに含めておくと、BPEL のAssign アクティビティからこんな感じで呼び出せる。
<assign name="Assign_1">

<copy>
<from expression="ora:processXSLT('exercise1.xsl',bpws:getVariableData('inputVariable','payload','/client:process'))"/>
<to variable="sum"/>
</copy>
</assign>

ora:processXSLTから*.xsl ファイルを実行するところまでは同じでも、いつもの BPEL の Transform とは主に以下のような点で違う。
  • transformation パターンのアノテーションがない。
  • XSLTがtextを出力している。
  • to で単純型の変数を指定して、これでXSLTの出力を受けている。
  • XSL に oracle-xsl-mapper インストラクションが無い

なんだかこの例だと手続き型の作法で書いたBPELコードと大差ないし、下手したら XSLT に不慣れな人には余計複雑に見えるようなった気がしないでもないが・・・

ただ、センスも腕もそこそこあるプログラマが、XSLT みたいなフツーのXML技術でできることを活用しないまま「BPELが不便すぎて」だとか「BPEL仕様の制約が・・・」なんてブツクサ言うのを見ると、ちょっともったいない気がする。第一、本人のスキルも十分生かされていないだろうし。

思うに、IDE のグラフィカルなデザイナ上では、XML 技術のわずかな部分しか使うことができない。入力XML要素から出力XML要素に線を引っ張るだけの作業がアホらしくなったら、いっそ 思い切って transform アノテーションを外してソース直接編集に切り替えた方が、自分にもチームにも品質にも、いろいろ良い効果があるかも。

2010年3月14日日曜日

Oracle BPEL/Java Embedding/正規表現

正規表現のグループ機能を使いたいが、matches(intputString, regexPattern) だとマッチングの成否を返すだけなので、Java Embedding を使ってみることにした。

環境:Fusion Middleware 11g + JDeveloper 11g

やりたい事は、例えば「テキストに含まれる最初の日付文字列から曜日を取り出す」といった感じの問題で、こんなふうにしたい。
<soapenv:Body>
<bpel:process>
<bpel:input>
こんにちは
今日の日付は 2010/03/13(土)
です。
</bpel:input>
</bpel:process>
</soapenv:Body>
<env:Body>
<processResponse xmlns="http://xmlns.oracle.com/OracleBpelTest1App_jws/Project1/BPELProcess1">
<result>土</result>
</processResponse>
</env:Body>

試しにBPEL デザイナ上でこんなコードを書いてみた。
更にソースエディタで、Java_Embedding_1 の直前に 以下のようにimport を記述する。
    <bpelx:exec import="org.w3c.dom.Element"/>
<bpelx:exec import="java.util.regex.*"//>

SoapUI から動かすと、期待したリクエスト/レスポンスが得られることが確認できた。

まあわりとすんなりいくが、以下、若干イラっとした点。
最初、org.w3c.dom.Element を import し忘れていたため、コンパイル時に普通に「シンボルが見つからない」という事になるが、コンパイラビューのメッセージには「クラスパスが正しくないので、正しく設定しろ」との文言が出る。これに騙されて、本来の原因に気づくまでクラスパス関連を確認し始めたりして、時間を無駄する羽目になる。JDeveloper はこういうミスリーディングな挙動が本当に多すぎてストレスがたまる。

2010年3月8日月曜日

Oracle BPEL 11g/Compensation

Oracle BPEL の Compensation の試し書き。


outerScope と innerScope の二重のスコープで、outerScope に faultHandler、innerScope に compensationHandler を定義するようなつくりにしてみた。

実行の流れとしては、innerScope 内の sayHi でレスポンスに文字列 "Hi!"を書き込んでからスコープを完了した直後、わざと fault を <throw>。この fault は outerScope の <faultHandler > でキャッチされるが、この<catch>節には innerScope の 補償が含まれているため、compensationHandler 内の sayBye が実行されて、レスポンスに "Bye!"が追記される。

これをSoapUI からの実行すると以下のようなレスポンスになる。
<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsa="http://www.w3.org/2005/08/addressing">
<env:Header>
・・・略
</env:Header>
<env:Body>
<processResponse xmlns="http://xmlns.oracle.com/WorkflowPatternsApp_jws/BasicCflowPrj/CancelTask1Process">
<result>Hi!</result>
<result>Bye!</result>
</processResponse>
</env:Body>
</env:Envelope>


やってみるまで知らなかったけど、fault を投げている throw アクティビティを innerScope の中に入れてしまうと、compensation ハンドラが実行されなくなる。完了したスコープのみが補償対象になり得るというのが、BPEL仕様らしい。

ちなみに workflowpatterns の記述を見ると、BPELではこの Fault とCompensationを使ってCancel Task パターンが実現されるとあるけど、JDeveloper 上のモデルと workflowpatterns サイトの図解が全然似てなくて、なんだかよくわからない。

2010年3月3日水曜日

Oracle BPEL/複数のXML変数をXSLTに渡す

BPELで、二つの変数を XSLT の入力として使いたい場合、11g なら doXSLTransform を使えば良いけど、10g だと未サポートなので bpelx:append で要素をひとつにまとめて processXSLT 関数に渡すことになる。

このとき追加先となる BPEL変数は、スキーマ定義から逸脱して well-formed ではあっても valid ではない形にもなるが、非valid な XMLでも、内部の処理で変数として使う分には、別段、問題ないらしい。(さすがに外部に公開するサービスではまずいだろうけど。)

実は、自分の場合、内部の処理でも valid なXMLでないと駄目だと思い込んでしまって、若干はまった。XMLスキーマを変更して内部処理用の新たな型を追加しようとしたものの、最近入った現場ではそれがしにくい事情があって、けっこう悩んだ。valid でなくても良いんだと気づいたら本当にしょうもない話だが…

ちなみに JDeveloper の XSL デザイナの恩恵は得られないけど、もともと大して便利なツールじゃないし、これは別にいいや。<annotation> を取り除いて、Transform じゃない単なる Assign の xpath 関数にしてした上でソースを直接編集しても、それほど手間は変わらない。

2010年3月1日月曜日

Oracle BPEL/Email activity

Fusion Middleware 11g の Oracle BPEL で、Emailアクティビティを使ってみる。

■ 環境
・Fusion Middleware 11g、JDeveloper 11g
・Radish 3.0.0 (SMTP サーバ)

■ サーバ設定
BPEL に含めたEmailアクティビティを動かす前に、サーバ側の設定が必要となる。これをやっておかないと、BPEL の実行そのものはエラーにならないけど、メールは送られなかったりする。

◆ 電子メール・ドライバ・プロパティ
以下のように電子メール・ドライバ・プロパティを開く。

ここで、"Outgoing"で始まるプロパティを適当に設定する。試用目的なので、OutgoingMailServer に localhost とだけ指定したが、ポートやら、SMTP認証する場合のユーザ名/パスワードやら、一通り設定できる。

◆ ワークフロー通知プロパティ
以下のようにワークフロー通知プロパティの設定ページを開く。通知モードのデフォルトは「なし」になっているので、電子メールを選択する。それだけ。

設定したら再起動。

■ BPEL定義
Emailアクティビティを含む簡単なBPELフローを定義する。
中身はこんな風に設定してみる。ドメイン名とユーザ名は Radish で定義しておく。


先に進む前に、ちょっと観察してみる。

パートナーリンク NotificationService_1 は Emailアクティビティを追加したときに勝手に生成される。

Emailアクティビティ を展開すると、内部で構成されているinvoke アクティビティから、このパートナーリンクの操作 sendEmailNotification を呼んでいるのが分かる。
NotificationService_1の wsdl を見るとバインディングに java:binding が指定されている。初めて見た。

■ 実行
デプロイして実行すると Radish の MAILBOX にメールが入っているのが分かる。

2010年2月27日土曜日

Oracle BPEL/ParalellSplit (2)

前回同様、Oracle BPEL での ParallelSplit の試行。

以下の環境
・Fusion Middleware 11g + JDeveloper 11g
・SoapUI

前回やってみた結果、Flow アクティビティ内部に置いた他サービスの同期呼び出し(invoke)は、本来は互いに結果受信を待ったりせずに並列実行されるのが望ましいが、実際、そうはならず逐次実行されてしまうことがわかった。つまり、並行する2つのブランチで実行される10秒ずつのサービスが、並列に10秒で実行されるのではなく、1つずつ合計20秒で実行されることになる。また、パートナーリンクの nonBlockingInvoke プロパティを設定しても、結局並列実行にはならなかった。

で、今回は次善の策として、明示的な非同期呼び出しを試した。

■ 試行-パートナーリンク
前回同様、受信XMLに含まれるミリ秒だけ待って、開始終了時間を返す外部サービスを適当に作った(ただし今回は非同期なサービスとした)。これをパートナーリンクで指定して BPEL から呼び出してみる。

まずこれを呼び出す BPEL 定義を、ブランチ一本の Flow アクティビティを使ってやってみる。
まあ、普通に問題なく動いた。

次にこのFlowアクティビティに、同じ構成のブランチを一本追加してみる。
あまり期待していなかったが、やっぱりエラーがでた。
<faultstring>receiveが競合しています.
類似したreceiveアクティビティが同じプロセスで宣言されています。.
別のreceiveアクティビティまたは同等のもの(現在のところ、pickアクティビティのonMessageブランチ)が、partnerLink "WaitAsyncProcess1"、操作名"processResponse"および相関セット"" (または対話ID)ですでに有効になっています。BPEL 1.1仕様の「Appendix A - Standard Faults」によると、このような状況ではフォルトがスローされます。
競合しているreceiveアクティビティを削除してからプロセスを再デプロイしてください。.</faultstring>

なるほど、受信系のアクティビティは、partnerLink、操作名、相関セットの組で一意に識別されるらしい。


というわけで、同じサービスについて別々の partnerLink を定義して、Flowアクティビティ以下の各ブランチで呼ぶようにしてみる。

なるほど上手くいった。

■ 試行2-相関セット
さらに相関セットも試してもみた。
  • まずブランチ1本の Flow から相関セットを指定して非同期呼び出しをやってみて確認。まあ当たり前だが、これは上手く行く。
  • 次に、ブランチを足してみる。試しに上で使ったのと同じ相関セットを指定してみると、「同じ相関セットは2度初期化できない」といった感じのエラーになる。
  • 上の相関セットと同じプロパティを用いて、別の相関セットを追加定義してみる。これは上手くいった。
  • プロパティとプロパティエイリアスを別途追加定義して、上で追加した相関セットに関連付けてみる。例外発生。
    → <detail>java.lang.NullPointerException
    at com.collaxa.cube.engine.delivery.CorrelationProperty.<init>(CorrelationProperty.java:53)
    at com.collaxa.cube.engine.delivery.DeliveryHelper.createCorrelationSet(DeliveryHelper.java:231)
    at com.collaxa.cube.engine.ext.common.ReceiveHandler.handleNormalInput(ReceiveHandler.java:178)
    ・・・以下略
    受信XML の同じ部分に別々のプロパティエイリアスを関連付けるのがNGという事なのか。NullPointerException なのでよく分からない。


■ 結び
結局、ある ParallelSplit の別々の分岐で同じサービスを invoke する場合、(1)パートナーリンクを別途定義する方法と、(2)同一プロパティに相関セットを別途定義する方法があるらしいことが分かった。

ただ、本当に正しいやり方なのか、何となく不安な感じもある(特に相関セットの方)。以外にネット上の言説も少なくて、これらのやり方で良いのか、またどちらのやり方が良いのかなど、確信が得られない。

あと、方法として問題なかったとしても、パートナーリンクや相関セットを定義する都合上、事前に分岐の数が決まっている必要があるので、FlowNでは使えないということになる。

元はといえば、Flow アクティビティの並列実行が、文字通り本当に並列実行できていれば問題ないのだけど、なんだかちょっと面倒くさい事になっている。

Oracle BPEL だからなのか、他製品もそうなのか・・・