HinemosのジョブネットをWebサービスAPI経由で実行する

HinemosにはWebサービスAPIと呼ばれる機能があり、機能をHTTP経由で使用できる。使用できるというか、ご本家のクライアントとマネージャのやりとりも全てそれ経由らしい。つまり、全ての機能をHTTP経由で利用できるということだ。

何がしたいか?

ジョブネット定義を自動で登録したい(キリッ

状況!

WebサービスAPIの資料や情報がやたら少ないorz

ググって出てくる数少ない参考サイトはレベルが高く「WebサービスAPIへアクセスするためのエンドポイント、またAPIが用意しているメソッドについては、Hinemosマネージャのソースコードに全て記載されていますので、ここでは詳しく触れません。」とのお言葉。そして、ジョブネット定義というヤヤコシイこと(かつ素直にオプションを買えば楽になりそうなこと)ではなく、ジョブネットの実行の例なのだ。とどめにJavaではなくPerlとかPythonの例なのだ。Javaは!?

そして私自身のJavaスキルはというと、「JavaからSOAPのサービス呼び出しってどうやるの?」な状況である。

どうする?

状況の通りいろいろと苦しい。ともかくWebサービスAPI経由でなにかできるところまでたどり着く必要がある。

というわけで、まずは「言語違いでも例が多少あるジョブネット実行をJavaからやってみよう」(その途中でいろいろ見えてくるだろう)となった。

環境は「Hinemosのインストール」でインストールした環境を使う。Hinemos用のJDKは7だが、作成するプログラムはJDK8で開発する。8でなくとも6以降ならどれでもよいはず。

やってみよう! スタブ作成

SOAPのサービスは WSDL という形式でカタログ的なものがサーバ側から提供される。
Hinemosのジョブ機能のカタログは http://<マネージャのホスト名>:8080/HinemosWS/JobEndpoint?wsdl にアクセスすることで確認できる。ジョブネット実行のために呼び出すrunJobサービス(参考サイトの例からこれを呼べば良いことは解った)の定義も含まれている。

上記の WSDL を読んでもピンとこないが、「SOAP Webサービスクライアントを作ろう(JAX-WS, Apache CXF 編)」を頼りに進めよう。
Java的(タイプセーフ)に事を進めるために、WSDL からスタブのクラス群を生成してくれるコマンドがある。それがJDK6から標準で取り込まれている「wsimport」コマンドだ。このコマンドで生成されたクラス群とJavaらしくやり取りすることで、それがSOAP Webサービスへのアクセスに自動的に置き換えられる。これはなかなかわかりやすい。

検証用に /tmp/sandbox ディレクトリを作成。

# mkdir /tmp/sandbox

作成したディレクトリに移動して wsimport を WSDL のURLをオプションに指定して実行。-keep は自動生成した .java ファイルも残しておくために指定。

# cd /tmp/sandbox/
# wsimport -keep http://garnet-vm09:8080/HinemosWS/JobEndpoint?wsdl
parsing WSDL...



Generating code...


Compiling code...

エラー無く完了したので生成結果を確認する。com/clustercontrol/ws/jobmanagement 下に RunJob.class などそれらしいクラスが生成されている。

# ls -l
total 4
drwxr-xr-x 3 root root 4096 May  1 05:41 com

# tree com
com
└── clustercontrol
    └── ws
        ├── calendar
        │&#160;&#160; ├── CalendarDetailInfo.class
        │&#160;&#160; ├── CalendarDetailInfo.java
        │&#160;&#160; ├── (中略)
        │&#160;&#160; ├── Ymd.class
        │&#160;&#160; └── Ymd.java
        ├── jobmanagement
        │&#160;&#160; ├── AddFileCheck.class
        │&#160;&#160; ├── AddFileCheck.java
        │&#160;&#160; ├── (中略)
        │&#160;&#160; ├── JobEndpoint.class
        │&#160;&#160; ├── JobEndpoint.java
        │&#160;&#160; ├── JobEndpointService.class
        │&#160;&#160; ├── JobEndpointService.java
        │&#160;&#160; ├── (中略)
        │&#160;&#160; ├── RunJob.class
        │&#160;&#160; ├── RunJob.java
        │&#160;&#160; ├── (中略)
        │&#160;&#160; └── UserNotFound.java
        └── (後略)

5 directories, 248 files

やってみよう! スタブを使用してWebサービスアクセス

スタブを使用してジョブネットを実行するためのプログラムとして JobKick.java を作成。

/tmp/sandbox/JobKick.java

import com.clustercontrol.ws.jobmanagement.*;
import javax.xml.ws.BindingProvider;
import java.util.Map;

public class JobKick {
    public static void main( String[] args ) {
        // アクセス先/認証情報
        final String ENDPOINT_URL = "http://garnet-vm09:8080/HinemosWS/JobEndpoint";
        final String USERNAME = "hinemos";
        final String PASSWORD = "hinemos";

        // 実行するジョブのジョブユニットIDおよびジョブID
        final String JOBUNIT_ID = "JobUnit01";
        final String JOB_ID = "JobNet01";

        JobEndpointService service = new JobEndpointService();
        JobEndpoint servicePort = service.getJobEndpointPort();

        // アクセス先設定/認証情報設定
        BindingProvider bp = (BindingProvider)servicePort;
        Map<String, Object> requestContext = bp.getRequestContext();
        requestContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, ENDPOINT_URL);
        requestContext.put(BindingProvider.USERNAME_PROPERTY, USERNAME);
        requestContext.put(BindingProvider.PASSWORD_PROPERTY, PASSWORD);

        // runJobサービス実行のための引数準備
        OutputBasicInfo obi = new OutputBasicInfo();
        JobTriggerInfo jti = new JobTriggerInfo();
            jti.setTriggerType(2);           // 手動投入
            jti.setTriggerInfo("hinemos");   // 実行ユーザ

        // 実行(やたら例外多い...)
        try {
            System.out.println("runJob starting...");

            String out = servicePort.runJob(JOBUNIT_ID, JOB_ID, obi, jti);

            System.out.println("runJob complted.");
            System.out.println("runJob Output: " + out);
        }
        catch ( FacilityNotFound_Exception e ) {
            e.printStackTrace(System.out);
        }
        catch ( HinemosUnknown_Exception e ) {
            e.printStackTrace(System.out);
        }
        catch ( InvalidRole_Exception e ) {
            e.printStackTrace(System.out);
        }
        catch ( InvalidUserPass_Exception e ) {
            e.printStackTrace(System.out);
        }
        catch ( JobInfoNotFound_Exception e ) {
            e.printStackTrace(System.out);
        }
        catch ( JobMasterNotFound_Exception e ) {
            e.printStackTrace(System.out);
        }
        catch ( JobSessionDuplicate_Exception e ) {
            e.printStackTrace(System.out);
        }
    }
}
runJobメソッドの引数

以下の3点を参考にして理解した。

  • 生成された com/clustercontrol/ws/jobmanagement/JobEndpoint.java (引数の数、型ぐらい)
  • Hinemos のソース HinemosManager/src_ws/com/clustercontrol/ws/jobmanagement/JobEndpoint.java ( どう使われるか、何を求めているか )
  • WebサービスAPIでジョブを起動する

ソースコード見るのはちょっと・・・な感じだったが WebサービスAPI のソースが src_ws 下に纏められており、確認しやすかった。ソースみてね、という言葉も理解できてきた(かも)。

HinemosManager/src_ws/com/clustercontrol/ws/jobmanagement/JobEndpoint.java から抜粋。

        /**         * ジョブを実行します。<BR>
         * 
         * JobManagementExecute権限が必要
         * 
         * @param jobunitId 所属ジョブユニットのジョブID
         * @param jobId ジョブID
         * @param info ログ出力情報
         * @param triggerInfo 実行契機情報
         * @throws JobMasterNotFound
         * @throws JobInfoNotFound
         * @throws HinemosUnknown
         * @throws FacilityNotFound
         * @throws InvalidRole
         * @throws InvalidUserPass
         * @see com.clustercontrol.jobmanagement.ejb.session.JobControllerBean#createJobInfo(String, String, NotifyRequestMessage, JobTriggerInfo}
         * @see com.clustercontrol.jobmanagement.session.JobRunManagementBean#runJob(String, String)
         */
        public String runJob(String jobunitId, String jobId, OutputBasicInfo info, JobTriggerInfo triggerInfo)
            throws FacilityNotFound, HinemosUnknown, JobInfoNotFound, JobMasterNotFound, InvalidUserPass, InvalidRole, JobSessionDuplicate
        {

実行するジョブネットはジョブユニットのIDおよびジョブネットのIDを指定することで一意になる。

JobEndpointService, JobEndpoint クラスの使用

runJobメソッド呼び出し部分がコア部分なのだが、service, servicePort の作成など段階を踏まなければならなかった。JobEndpointServiceクラス、JobEndpointクラスを使うことをどうやって導き出したのか覚えが無いのだが、おそらくこんな順番だったと記憶。

  1. RunJob.java の中身をみて、実行に結びつきそうなメソッドが無いと落胆。
  2. com/clustercontrol/ws/jobmanagement 下を grep -i runjob *.java 検索し、runJob メソッドを JobEndpoint インターフェースが持つ事を認識。でもインターフェースだから実行できん!
  3. com/clustercontrol/ws/jobmanagement 下を grep -i JobEndpoint *.java 検索し、JobEndpointService の getJobEndpointPort メソッドが生成してくれると理解。「SOAP Webサービスクライアントを作ろう(JAX-WS, Apache CXF 編)」でも以下の2ステップで getMessage サービスをメソッドとして持つインスタンスを生成しているから、おそらくこれで正解と結論。
public String execute(String text, int num) {
  ZaneliWS ws = new ZaneliWS();
  ZaneliWSSoap soap = ws.getZaneliWSSoap();
  return soap.getMessage(text, num);
}
認証情報の設定

下記のコードが認証情報を設定している部分である。
web service clients with wsimport and jax-ws」の例を参考にしてなんとかうまくいった。

        BindingProvider bp = (BindingProvider)servicePort;
        Map<String, Object> requestContext = bp.getRequestContext();
        requestContext.put(BindingProvider.USERNAME_PROPERTY, USERNAME);
        requestContext.put(BindingProvider.PASSWORD_PROPERTY, PASSWORD);

この設定がないと下記の様な例外が発生してしまう。まぁ、認証情報与えずにジョブネット実行できちゃだめだよね。

# java JobKick
runJob starting...
com.clustercontrol.ws.jobmanagement.InvalidUserPass_Exception: Authorization does not exist
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:422)
	at com.sun.xml.internal.ws.fault.SOAPFaultBuilder.createException(SOAPFaultBuilder.java:135)
	at com.sun.xml.internal.ws.client.sei.StubHandler.readResponse(StubHandler.java:238)
	at com.sun.xml.internal.ws.db.DatabindingImpl.deserializeResponse(DatabindingImpl.java:189)
	at com.sun.xml.internal.ws.db.DatabindingImpl.deserializeResponse(DatabindingImpl.java:276)
	at com.sun.xml.internal.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:104)
	at com.sun.xml.internal.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:77)
	at com.sun.xml.internal.ws.client.sei.SEIStub.invoke(SEIStub.java:147)
	at com.sun.proxy.$Proxy35.runJob(Unknown Source)
	at JobKick.main(JobKick.java:36)

やってみよう! 実行

無事にジョブユニット JobUnit01 のジョブネット JobNet01 が実行された。

  • runJobメソッドから返る文字列は [Session ID] である。
  • ジョブネットの完了前に runJob メソッドは制御が返ってくる(キックのみが仕事)。