Axis2 機能拡張モジュールの作成方法 ~SOAP レスポンスにヘッダーを追加する~

Axis2 で Web サービスを実装しました。メッセージの内容は、AXIOM(Axis2 の DOM?)を使って生の XML で扱う方法を採用しました。そんな中、SOAP レスポンスにヘッダーを追加する必要があり、方法を調べました。

今回作成したい Web サービスの内容

お客様サポートの問い合わせをリクエストとして受け取り、それに対する返事をレスポンスとする Web サービスを作成することにします。とはいえ、Web サービスの内容自体は重要ではなく、「SOAP レスポンスにヘッダーを追加し、「お問い合わせ番号」を設定する」ことを目的とします。

SOAP リクエストは次の通りです。

<soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope">
   <soapenv:Body>
      <Inquire xmlns="urn:userSupport">
         <UserName>みかん大好き</UserName>
         <Content>△△店では「ガ〇ン、とみかん」マルチパックの取り扱いはありますか?</Content>
      </Inquire>
   </soapenv:Body>
</soapenv:Envelope>

期待する SOAP レスポンスは次の通りです。ヘッダーに「お問い合わせ番号」が設定されているところがポイントです。

<soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope">
   <soapenv:Header>
      <InquiryNumber xmlns="urn:userSupoport">INQ-12345</InquiryId>
   </soapenv:Header>
   <soapenv:Body>
      <InquireResponse xmlns="urn:userSupoport">
         <Content>お問い合わせを受付けました。後ほど改めましてご返信差し上げます。お問い合わせ番号は「INQ-12345」です。</Content>
      </InquireResponse>
   </soapenv:Body>
</soapenv:Envelope>

SOAP ヘッダーの追加はどこで行うか

サービス クラスの実装は、次の通りです。サービス メソッドの戻り値は、あくまでレスポンスのボディ部分のみです。

public class UserSupport {
    // 問い合わせを受けるサービス メソッド
    public OMElement Inquire(OMElement requestElement) {
        // ここでリクエストの XML(requestElement パラメーター)から、問い合わせ内容などを取得します(省略)。

        // レスポンスの XML を作成します。
        OMFactory omFactory = OMAbstractFactory.getOMFactory();

        OMNamespace ns = omFactory.createOMNamespace("urn:userSupoport", "");

        OMElement responseElement = omFactory.createOMElement("InquireResponse", ns);

        OMElement contentElement = omFactory.createOMElement("Content", ns, responseElement);
        contentElement.setText("お問い合わせを受付けました。後ほど改めましてご返信差し上げます。お問い合わせ番号は「INQ-12345」です。");

        return responseElement;
    }
}

サービスを呼び出した際の SOAP レスポンスは次の通りです。当然ですが、この時点ではヘッダーがありません。

<Envelope xmlns="http://www.w3.org/2003/05/soap-envelope">
   <Body>
      <InquireResponse xmlns="urn:userSupport">
         <Content>お問い合わせを受付けました。後ほど改めましてご返信差し上げます。お問い合わせ番号は「INQ-12345」です。</Content>
      </InquireResponse>
   </Body>
</Envelope>

それでは、ヘッダーを追加したいときは、どうするのか?ということになるのですが、結論としては、サービス クラスだけでは追加できません。別途、『モジュール』というものを作成する必要があります。

『モジュール』『フロー』『フェーズ』『ハンドラー』とは

そもそも『モジュール』とはなんでしょうか。また、モジュールを作成するにあたり『フロー』『フェーズ』『ハンドラー』という概念が出てくるので、先にこれらについてまとめておきます。

  • Axis2 では、『モジュール』を作成することにより、独自の機能拡張を行うことができる。
  • SOAP レスポンスにヘッダーを追加するには、この『モジュール』を作成することが必要。
  • Axis2 の処理は一連の『フロー』で構成されている。また、各フローは一連の『フェーズ』で構成されている。作成したモジュールは、どのフローで実行するかを指定してやる必要がある。

『フロー』とは

『フロー』は、Axis2 エンジンが実行する処理の流れを、4 種類の論理的な区分に分けたものです。4 種類の区分とは、InFlowInFaultFlowOutFlowOutFaultFlow です。

フロー意味
InFlow メッセージの入力を処理するフロー
InFaultFlow メッセージの入力(Fault 時)を処理するフロー
OutFlow メッセージの出力を処理するフロー
OutFaultFlow メッセージの出力(Fault 時)を処理するフロー

『フェーズ』とは

『フェーズ』とは、1 つのフローをさらに論理的に区切ったものです。つまり、1 つのフローの中に、フェーズが複数存在します。また、フローはあらかじめ Axis2 でによって 4 種類に決まっていますが、フェーズは何個でも存在でき、自作のフェーズを新たに定義することもできます。

『ハンドラー』とは

『ハンドラー』とは、フェーズに対してあらかじめ登録しておくものです。Axis2 エンジンによって対象のフェーズが実行される時に、登録されているハンドラーの処理が呼び出されます。

文章だとややこしいので、図にしました。

Axis2 のフローとハンドラー
フローとハンドラー

モジュールを作成する

モジュールを作成するには、いろいろなファイルを作成または編集する必要があり、けっこう大変です。具体的には、次の作業を行うことになります。

  1. モジュール クラスを実装する
  2. ハンドラー クラスを実装する
  3. フェーズを定義する(axis2.xml ファイルの編集)※任意
  4. モジュール定義を作成する(module.xml ファイルの作成)
  5. サービスにモジュールを関連付ける(services.xml ファイルの編集)

モジュール クラスを実装する

まず、Module インターフェイスを実装したクラスを作成します。このインターフェイスには 5 つのメソッドがあり、モジュールの初期化時や終了時の処理などを実装しますが、今回は特に何もすることがないので、処理は空にしておきます。

package axis2module.module;

import org.apache.axis2.AxisFault;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.description.AxisDescription;
import org.apache.axis2.description.AxisModule;
import org.apache.axis2.modules.Module;
import org.apache.neethi.Assertion;
import org.apache.neethi.Policy;

public class UserSupportModule implements Module {
    public void init(ConfigurationContext configurationContext, AxisModule axisModule) throws AxisFault {
    }

    public void engageNotify(AxisDescription axisDescription) throws AxisFault {
    }

    public boolean canSupportAssertion(Assertion assertion) {
        return false;
    }

    public void applyPolicy(Policy policy, AxisDescription axisDescription) throws AxisFault {
    }

    public void shutdown(ConfigurationContext configurationContext) throws AxisFault {
    }
}

ハンドラー クラスを実装する

次に、AbstractHandler クラスを継承したクラスを作成します。invoke メソッドをオーバーライドし、ここに実際に行いたい処理、つまり今回であれば SOAP ヘッダーを追加する処理を実装します。

package axis2module.module;

import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMFactory;
import org.apache.axiom.om.OMNamespace;
import org.apache.axiom.soap.SOAPEnvelope;
import org.apache.axiom.soap.SOAPHeader;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.handlers.AbstractHandler;

public class AddSoapHeaderHandler extends AbstractHandler {
    public InvocationResponse invoke(MessageContext ctx) {        
        // SOAP ヘッダーを追加します。
        OMFactory omFactory = OMAbstractFactory.getOMFactory();

        SOAPEnvelope envelope = ctx.getEnvelope();
        SOAPHeader header = envelope.getHeader();

        OMNamespace ns = omFactory.createOMNamespace("urn:userSupoport", "");

        OMElement inquiryIdElement = omFactory.createOMElement("InquiryNumber", ns, header);
        inquiryIdElement.setText("INQ-12345");

        // 他の処理を継続して実行します。
        return InvocationResponse.CONTINUE;
    }
}

フェーズを定義する

Axis2 の設定ファイル(axis2.xml)には、以下の通り、はじめから数種類のフェーズが定義されています。

<!-- ================================================= -->
<!-- Phases                                            -->
<!-- ================================================= -->
<phaseOrder type="InFlow">
    <!-- System predefined phases -->
    <phase name="Transport">
        :
    </phase>
    <phase name="Addressing">
        :
    </phase>
    <phase name="Security"/>
    <phase name="PreDispatch"/>
    <phase name="Dispatch" class="org.apache.axis2.engine.DispatchPhase">
        :
    </phase>
    <phase name="RMPhase"/>
    <!-- System predefined phases -->
    <!-- After Postdispatch phase module author or service author can add any phase he want -->
    <phase name="OperationInPhase">
        :
    </phase>
    <phase name="soapmonitorPhase"/>
</phaseOrder>
<phaseOrder type="OutFlow">
    <!-- user can add his own phases to this area -->
    <phase name="soapmonitorPhase"/>
    <phase name="OperationOutPhase"/>
            
    <!-- system predefined phase -->
    <!-- these phase will run irrespective of the service -->
    <phase name="RMPhase"/>
    <phase name="PolicyDetermination"/>
    <phase name="MessageOut"/>
    <phase name="Security"/>
</phaseOrder>
<phaseOrder type="InFaultFlow">
    <phase name="Addressing">
        :
    </phase>
    <phase name="Security"/>
    <phase name="PreDispatch"/>
    <phase name="Dispatch" class="org.apache.axis2.engine.DispatchPhase">
        :
    </phase>
    <phase name="RMPhase"/>
    <!-- user can add his own phases to this area -->
    <phase name="OperationInFaultPhase"/>
    <phase name="soapmonitorPhase"/>
</phaseOrder>
<phaseOrder type="OutFaultFlow">
    <!-- user can add his own phases to this area -->
    <phase name="soapmonitorPhase"/>
    <phase name="OperationOutFaultPhase"/>
    <phase name="RMPhase"/>
    <phase name="PolicyDetermination"/>
    <phase name="MessageOut"/>
    <phase name="Security"/>
</phaseOrder>

定義済みフェーズにおいてモジュールを実行する場合は、設定を編集する必要はありません。モジュールを実行するために専用のフェーズを追加する場合は、定義済みフェーズと同じ記述方法で、フェーズを追加します。

今回は定義済みフェーズである OperationOutPhase にて、モジュールを実行することにします。よって、設定ファイルは編集しませんでした。

モジュール定義を作成する

次に、モジュールの定義を記述するファイルを作成します。ファイル名は module.xml とします。

<!-- モジュール名を指定し、モジュール クラスを指定します。ここで付けたモジュール名は、後でサービスとモジュールを関連付ける時に使用します。 -->
<module name="userSupport" class="axis2module.module.UserSupportModule">
    <!-- 実行するフローを指定します。 -->
    <OutFlow>
        <!-- 使用するハンドラー クラスを指定し、名前をつけます。 -->
        <handler name="addSoapHeaderHandler" class="axis2module.module.AddSoapHeaderHandler">
            <!-- 実行するフェーズを指定します。 -->
            <order phase="OperationOutPhase" />
        </handler>
    </OutFlow>
</module>

サービスにモジュールを関連付ける

最後に、サービスとモジュールを関連付けます。サービス定義ファイル(services.xml)を次のように編集します。

<service name="UserSupport">
    <Description>Axis2 module test.</Description>

    <parameter name="ServiceClass" locked="false">axis2module.service.UserSupport</parameter>

    <!-- このサービス内のすべてのメソッドでモジュールを実行する場合は、<service> 要素の直下に <module> 要素を記述することもできます。 -->
    <!-- <module ref="userSupport"/> -->

    <operation name="Inquire">
        <!-- サービス内の個々のメソッドで実行するモジュールを指定する場合は、対象の <operation> 要素の直下に <module> 要素を記述します。 -->
        <!-- ref 属性には、module.xml で定義したモジュール名を指定します。 -->
        <module ref="userSupport"/>
    </operation>
</service>

ここまでを行ってから Web サービスを呼び出すと、ようやく SOAP ヘッダーが付与されたレスポンスが出力されます。ヘッダーを追加するだけにしてはけっこう大変です…。ですが、この手法を知っておけばヘッダーを追加するだけではなくいろいろな機能を追加できますし、作成したモジュールやハンドラーは再利用することもできます。

デプロイ

上記の一連の作業を、私は IDE(Eclipse)上で試していたので、クラスや設定ファイルを生で扱っていたのですが、実行環境にデプロイするときは、モジュール一式を『mar ファイル』にする必要があります。mar ファイルは、拡張子が「.mar」のファイルで、『jar ファイル』と同じように作成します。Axis2 のサービスを『aar ファイル』にするのと同じ考え方でしょう。

プロジェクトの構成

最終的なプロジェクトの構成は、次のようになりました。

[Eclipse] プロジェクトの構成
プロジェクトの構成

kpdn

お寿司とゲームと動物が好きな、フリーランスのエンジニアです。フロントエンドからインフラまで日々奮闘中です。最近は物忘れがどんどんがひどくなってきました。

コメントを残す