misima RESTful Web Service Java クライアント

JAX-RS RESTful Web Service について二回に分けて記事を記した。

今日は Apache Wink 1.4 ライブラリを用いた Java REST クライアント・プログラムを書いてみた。Eclipse 開発環境(Eclipse Java EE IDE for Web Developers. Version: Kepler Service Release 1. Build id: 20130919-0819)にて実装するメモを残しておく。端末からコマンド入力の形で実行する,いわゆるコンソールプログラムであると同時に,標準入出力で変換前後テキストを読み書きするフィルタである。この構造にしておくと,複数のファイルを一括してまとめて処理出来たり,様々なプログラムから簡易に呼び出せたりして便利である。

クラス設計

設計内容とその機能を担当する Java クラス・ソースは以下のとおり。

  • 標準入力から変換対象テキストを読み,コマンドライン引数に指定した misima 変換オプションで,misimaRESTful Web Service に接続し,旧字・旧仮名遣い変換を行い,結果を標準出力に書く。実行メイン。(misimaRESTfulConvert.java)
  • コマンドライン引数を解析し,エラーチェック並びに,サーバに送信する変換オプションの組み立てを行う。Commons CLI クラスライブラリを使用する。(misimaOptions.java)
  • サーバに POST する変換対象テキストを標準入力から読み出して蓄積する。(misimaReader.java)
  • 接続先の情報(ホスト,アプリケーションパス,ユーザコード)はプロパティファイルで与えられるようにする。プロパティファイルのない場合はデフォルトの接続先を使用する。(misimaProperties.java)

Eclipse 開発準備作業

Apache Wink 1.4Apache Commons CLI 1.2 を使うので,バイナリパッケージをダウンロードして解凍しておく。

まず,Eclipse 上にプロジェクトを新規作成する。プロジェクト名は misimaRESTfulClient とする。File - New - Project... - Java Project でプロジェクト名を指定して作成する。

misimaRESTfulClient が参照する,ビルドに必要な外部ライブラリを追加する。Build Path - Add External Archives... によって,以下の jar ファイルを追加してゆく。Eclipse Package Explorer のプロジェクト内の Referenced Libraries にこれらが表示される。(図 1.)

  • wink-client-1.4.jar: Apache Wink Client API
  • wink-common-1.4.jar: Apache Wink 共通
  • geronimo-jaxrs_1.1_spec-1.0.jar: JAX-RS 標準 API
  • slf4j-api-1.6.1.jar: slf4j ロガー API
  • slf4j-simple-1.6.1.jar: slf4j 実装
  • commons-cli-1.2.jar: Commons CLI コマンドライン解析
20140225-eclipse-1.png
図 1. Referenced Libraries

Java プログラム

設計のところで触れた Java クラスのソースを,src - (default package) 配下に順次作成する。個別の Java コードについて以下に示す。

misimaRESTfulConvert は RESTful Web Service にアクセスするクライアントの中核クラスである。Apache Wink RestClient インスタンスを生成し,Resource クラスをこれにバインドしてヘッダを設定し,post メソッドで変換対象 XML データを misimaRESTful Web Service サーバに送信する。結果を ClientResponse クラスで受け取って,accept 指定に応じたエンティティ(この場合,String)として取り出す。これら,Web Service とのコンタクト部分は以下の数行である(ソース全体のなかでハイライト表示してある)。API の詳しい解説は Apache Wink Developer Guide を参照のこと。

RestClient client = new RestClient();
Resource resource = client.resource(host + path);
resource.contentType("application/xml");
resource.accept("text/plain");
ClientResponse response = resource.post(pxml);
System.out.print(response.getEntity(String.class));
import org.apache.wink.client.ClientResponse;
import org.apache.wink.client.Resource;
import org.apache.wink.client.RestClient;
 
/**
 *  misimaRESTfulConvert.java - misima RESTful Web Service Client
 *  - コマンドライン引数を解析する
 *  - 変換対象の UTF-8 テキストを読む
 *  - 接続先プロパティを取得する
 *  - POST XML を組み立てる
 *  - misimaRESTful Web Service に接続して変換結果を取得する
 *  Copyright(c) 2014, isao yasuda, All Rights Reserved.
 */
public class misimaRESTfulConvert {
    /** connect host */
    private static String host = "http://yasuda.homeip.net:8080";
    /** RESTful application path */
    private static String path = "/misimaRest/misima/convert";
    /** usercode */
    private static String ucod = "noxinsomniae201402";
    /** Property file */
    private static String prop = ".misimaRESTful-properties";
 
    /** Convert main */
    public static void main(String[] args) {
        /** コマンドライン引数解析 */
        String opts = null;
        try {
            misimaOptions mopt = new misimaOptions(
                    "misimaRESTfulConvert", args);
            if (mopt.parse()) {
                opts = mopt.getmisimaOptions();
            } else {
                System.exit(-1);
            }
        }
        catch (Exception e) {
            System.err.print(e.getMessage());
            System.exit(-1);
        }
 
        /** 変換対象テキスト取得 */
        String target = null;
        try {
            misimaReader mr = new misimaReader();
            target = mr.read();
        }
        catch (Exception e) {
            System.err.println("input file error.");
            System.exit(-1);
        }
 
        /** プロパティ取得 */
        try {
            misimaProperties mprop = new misimaProperties(prop);
            String hostp = mprop.getValue("host");
            if (hostp != null) host = hostp;
            String pathp = mprop.getValue("path");
            if (pathp != null) path = pathp;
            String ucodp = mprop.getValue("ucode");
            if (ucodp != null) ucod = ucodp;
        }
        catch (Exception e) {
            System.err.println("Properties not found. Running by default.");
        }
 
        /** Building XML for POST */
        String pxml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
                "<misimaRESTful>" +
                "<misimaParam>" + opts + " -q</misimaParam>" +
                "<misimaTarget>" + target + "</misimaTarget>" +
                "<misimaUsercode>" + ucod + "</misimaUsercode>" +
                "</misimaRESTful>";
 
        /** Connect misimaRESTful Web Service */
        try {
            RestClient client = new RestClient();
            Resource resource = client.resource(host + path);
            resource.contentType("application/xml");
            resource.accept("text/plain");
            ClientResponse response = resource.post(pxml);
            System.out.print(response.getEntity(String.class));
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
 
}

misimaOptions はコマンド引数を解析するクラスである。Apache Commons CLI API を使用している。

import org.apache.commons.cli.*;
 
/**
 *  misimaOptions.java - コマンドライン引数を解析する
 *  Copyright(c) 2014, isao yasuda, All Rights Reserved.
 */
public class misimaOptions 
{
    /** ユーザ指定引数 */
    private String[] cargs = null;
    /** 解析済みオプション文字列 */
    private String opts = null;
    /** オプション */
    private Options options = null;
    /** Program name */
    private String progname = null;
 
    /** Constructor */
    public misimaOptions(String progname, String[] args) {
        this.progname = progname;
        this.cargs = args;
        options = new Options();
        OptionBuilder.withArgName("c|h|u|a");
        OptionBuilder.hasOptionalArg();
        OptionBuilder.withDescription("旧字変換 " + 
                "<c:UTF-8; h:数値参照; u:TeX \\UTF; a:TeX \\CID>");
        options.addOption(OptionBuilder.create("s"));
        options.addOption("k", false, "旧仮名遣い変換");
        options.addOption("y", false, "用語用字変換");
        options.addOption("i", false, "繰返し符号変換");
        options.addOption("t", false, "単純補正変換");
        options.addOption("n", false, "仮名反転変換");
        options.addOption("h", false, "ヘルプ");
    }
 
    /** 解析 */
    public boolean parse() {
        CommandLineParser parser = new PosixParser();
        StringBuffer margs = new StringBuffer();
        try {
            CommandLine cl = parser.parse(options, cargs);
            // help
            if (cl.hasOption("h")) {
                showUsage(options);
                return false;
            }
            // misima command line args generate
            if (cl.hasOption("s")) {
                String sopt = cl.getOptionValue("s");
                if (sopt.equals("c") || sopt.equals("h") ||
                    sopt.equals("u") || sopt.equals("a")) {
                } else {
                    System.err.println("Unrecognized argument: " + sopt);
                    showUsage(options);
                    return false;
                }
                margs.append("-s ");
                margs.append(sopt + " ");
            }
            if (cl.hasOption("k")) margs.append("-k ");
            if (cl.hasOption("y")) margs.append("-y ");
            if (cl.hasOption("i")) margs.append("-i ");
            if (cl.hasOption("t")) margs.append("-t ");
            if (cl.hasOption("n")) margs.append("-n ");
            opts = margs.toString();
        } catch (ParseException e) {
            System.err.println(e.getMessage());
            showUsage(options);
            return false;
        }
        return true;
    }
     
    /** 解析済みオプション取得 */
    public String getmisimaOptions() {
        return opts;
    }
 
    /** Usage message */
    private void showUsage(Options options) {
        HelpFormatter hf = new HelpFormatter();
        hf.printHelp(progname, options, true);
    }
 
}

misimaReader はテキストファイルストリームを入力し文字列変数に蓄積するクラスである。コンストラクタの引数に指定した入力ストリームからデータを読む。引数がない場合は標準入力を読む。misimaRESTful クライアントでは後者のインスタンスを使っている。

import java.io.*;
 
/**
 *  misimaReader.java - misima 変換対象テキスト入力
 *  変換対象のテキストを UTF-8 として入力し,文字列に格納する
 *  Copyright(c) 2014, isao yasuda, All Rights Reserved.
 */
public class misimaReader
{
    /** リーダ(UTF-8) */
    BufferedReader  it;
    /** 入力文字バッファ */
    CharArrayWriter ot;
    /** 入力ストリーム */
    InputStream is;
 
    /** misimaReader を InputStream 指定で新たに作成する.
     * @param inputs InputStream オブジェクト
     */
    public misimaReader(InputStream inputs)
    {
        is = inputs;
    }
 
    /** misimaReader を標準入力指定で新たに作成する. */
    public misimaReader()
    {
        is = System.in;
    }
 
    /**
     * 入力テキストを文字列に格納する.
     * @return 変換対象テキスト文字列
     */
    public String read() throws IOException {
        int c;
        String tText = null;
 
        it = new BufferedReader(new InputStreamReader(is, "UTF-8"));
        ot = new CharArrayWriter();
 
        while ((c = it.read()) != -1)
            ot.write(c);
        it.close();
        tText = ot.toString();
        ot.close();
        return tText;
    }
 
}

misimaProperties.java についてはサーバ実装のところで掲載したものとまったく同じなので,そちらを参照。ここでは割愛する。

Runnable jar の生成

ビルドがうまく終わったら,runnable jar (実行可能 jar) ファイルを作成する。misimaRESTfulClient から参照するライブラリをすべて含めた実行可能形式で jar ファイルを生成しておけば,実行時のクラスパス設定がラクである。その Eclipse 操作手順は以下のとおり。

  1. プロジェクトの Properties から,Run/Debug Settings を選択する。
  2. New... ボタン - Java Application を選択し OK をクリックする。すると Edit launch configuration properties 画面が表示される。
  3. ここで Name: を適宜指定する。プロジェクト名と同じでよい。
  4. Main タブで Project: にプロジェクト名を,Main Class: misimaRESTfulConvert を指定する。(図 2.)
  5. プロジェクトの Export... から,Java - Runnable JAR File を選択する。すると Runnable JAR File Specification 画面が表示される。
  6. Launch configuration: に対し,Edit launch configuration properties 画面で作成した構成を選択する。
  7. Export destination: に出力ファイル名 misimaRESTfulClient.jar を指定する。
  8. Library handling: Extract required libraries into generated JAR (必要なライブラリを展開して jar に格納) を選択する。
  9. Finish ボタンで jar ファイルが生成される。(図 3.)
20140225-eclipse-2.png
図 2. Edit launch configuration properties 画面
20140225-eclipse-3.png
図 3. Runnable JAR File Specification 画面

試験

以上でプログラムが出来上がった。試験用のテキストファイル test.txt とプロパティファイル .misimaRESTful-properties を書いて,試験してみる。misimaRESTfulClient.jar は runnable なので,java -jar misimaRESTfulClient.jar misimaオプション でも,java -cp misimaRESTfulClient.jar misimaRESTfulConvert misimaオプション でも起動可能である。実行の様子は以下のとおり。

$ cat .misimaRESTful-properties
# misimaRESTful Client プロパティファイル
# 接続先ホスト
host=http://yasuda.homeip.net:8080
# misimaRESTful Web Service パス
path=/misimaRest/misima/convert
# ユーザコード(暫定試行用)
ucode=noxinsomniae201402
$ cat test.txt
森鴎外はこう言い,内田百間を団扇であおいだ。
僕は黙っていますよ。
$ java -jar misimaRESTfulClient.jar -h
usage: misimaRESTfulConvert [-h] [-i] [-k] [-n] [-s <c|h|u|a>] [-t] [-y]
 -h             ヘルプ
 -i             繰返し符号変換
 -k             旧仮名遣い変換
 -n             仮名反転変換
 -s <c|h|u|a>   旧字変換 <c:UTF-8; h:数値参照; u:TeX \UTF; a:TeX \CID>
 -t             単純補正変換
 -y             用語用字変換
$ java -cp misimaRESTfulClient.jar \
misimaRESTfulConvert -kyitn -s c < test.txt
森鷗外ハカウ言ヒ,內田百閒ヲ團扇デアフイダ。
僕ハ默ツテヰマスヨ。
$

企業ネットワークでは普通に設けられているプロキシ環境においても,以下のようにすれば,プロキシを越えて misima RESTful Web Service にアクセスできる。

java -Dhttp.proxyHost=プロキシ・ホスト名 \
     -Dhttp.proxyPort=プロキシ・ポート番号 \
     -Dhttp.proxyUser=プロキシ・ユーザ名 \
     -Dhttp.proxyPassword=プロキシ・パスワード \
-cp misimaRESTfulClient.jar \
misimaRESTfulConvert -kyitq -s c < input.txt

参考文献

RESTful Webサービス
Leonard Richardson Sam Ruby
オライリー・ジャパン

[ 2015/02/02 付記 ]
misima RESTful Web Service は 2014/12 より友人のためだけの限定公開といたしました。上記ユーザ認証コードでは動作しません。