何がしたかったのか
自分で作った、とあるサイトをスクレイピングしたものをJSONで取得できるオレオレWebAPIを、Sotaから呼び出して、返ってきた内容を喋らせようと思い、HTTPでRESTなAPIを呼び出す実装をSotaにしたかった。
Eclipseでやるのが吉な気がするのだが、Vstone Magicオンリーでどこまでできるかやってみようと思いたち、やってみた。
構成
自分で作ったオレオレWebAPIと、APIが叩かれたタイミングでスクレイピングしに行く実装はPythonで行って、AWSのEC2上に放り込んで実行しておく。
Sotaで標準の使用言語はJavaなので、JavaのREST関連の仕様であるJSR311(JAX-RS)の実装のJerseyを使って、上記のオレオレサービスを呼び出すようなクライアントを組み込むような構成でやってみた。
オレオレサービスとJavaのクライアント側作成に使ったライブラリ等は以下の通り。
WebAPI側
- Python 3.5.1
- Tornado 4.3(WebAPIを作成した際に利用したフレームワーク)
- Tornado-JSON 1.2.2(JSONを返すのに便利なモジュール)
クライアント側
- Java 8
- Jersey 2.22.2(JSR311の実装)
- Jackson 2.7.3(JerseyのクライアントでJSONを扱うために利用)
Talkクラスに処理を追加
実際にRESTでサービスを呼び出して、返ってきたテキストをお話させる部分の実装は、以前にチュートリアルなどで作っていたTalkクラスに、処理を追加して行ってみた。
やったことは以下。
外部アーカイブファイルの追加とインポートの追加
JerseyやJacksonをつかっているので、それらに関連するjarファイルの追加とTalkクラスのインポート文に必要なものを追加している。
jarファイルの追加については、ファイルメニュー→「ファイルの追加」→「参照jar」から追加する。追加したのは以下のjarファイル(列挙がめんどいのでエクスプローラの画面キャプチャですませちゃってます。すみません。)。
この辺り、mavenやgradleを使えば自動で依存関係も補完してくれるので、普段個別にダウンロードとかはしないけど、今のところVstoneMagicとmavenやgradleなんかを組み合わせるやり方(あるのかな?)がないので、事前にEclipseでSotaの実装関係なくJerseyのみのスパイクをしていた時に参照していたjarを突っ込んでいる。個々のjarがなんなのかは、ここでは割愛。
ファイル追加ができたら、Talkクラスのプロパティの「import」から必要なインポート文を追加する。こちらのインポート文もEclipseでのスパイク時に補完されたものを追加している。
メソッドブロックを追加して処理を追加
今回はサービスを呼び出してお話する部分をまとめてメソッドにしてしまった。設計的にはサービス呼び出して中身を返すところまでを一つのメソッドにするほうがよいと思うが、諸々まとめて書いて試したかったこともあり、発話まで一つのメソッドにした。
追加したメソッドブロック(speechLatestDiaryという名前のやつ)には以下を追加してある。
- 変数宣言ブロック(dateAndTitle):日付とタイトルを格納しておくString型の変数
- 変数宣言ブロック(diaryBody):本文を句点区切りで格納しておくString配列型の変数
- 自由記述処理ブロック:Jerseyを使ってサービスを呼び出すための処理を記述
- 発話ブロック(日付とタイトル):日付とタイトルの読み上げ
- forブロック:diaryBodyに格納した句点区切りの文字列を順番に取り出す
- 発話ブロック(本文):diaryBodyから取り出した文字列の発話
変数宣言は2つ使っていて、それぞれ日付とタイトル、本文を格納する。サービスの呼び出しについては、自由記述処理ブロックを使って書いてみた。
本文の発話については、返ってきたものをそのまま発話ブロックに渡してしまうと、本文が長すぎて一回の呼び出し文字数の制限に引っかかってしまうようだったので、句点区切りでもっておいて、繰り返し発話させることにした。その際、forブロックを使っているのだが、ここのループカウンタの条件記述はブロックの自由入力をつかって、diaryBodyのlengthを取るようにした。発話についてもsay_wordsの自由入力でdiaryBodyをカウンタiを添え字として中身を取り出して発話するようにしてみた。
自由記述処理ブロックの中身は以下のような実装にしてある。JerseyとJacksonでJSONで返ってくる結果の処理をしている。JerseyとJacksonの組み合わせで、BeenにJSONを自動バインドしてくれるので、そのためのクラスは後述するが、別途追加している。細かい説明は割愛するが、プロキシの設定等もろもろは会社の環境からやる場合に必要だったため追加してあって、Jersey+Jacksonによる処理の本体部分はJacksonをClientConfigに登録している(registerメソッドを呼び出している部分)記述以下の部分。先述したJSONをバインドするクラスをリクエストを呼び出す際に指定している。
String URL = "http://xxxxxxx";
Proxy proxy = new Proxy(Type.HTTP, new InetSocketAddress("xxx.xx.xx.x",8080));
HttpUrlConnectorProvider hcdp = new HttpUrlConnectorProvider();
hcdp.connectionFactory(url -> (HttpURLConnection) url.openConnection(proxy));
ClientConfig config = new ClientConfig().register(JacksonFeature.class);
config.connectorProvider(hcdp);
Client client = ClientBuilder.newClient(config);
WebTarget target = client.target(URL);
Diary diary = target.request(MediaType.APPLICATION_JSON).get(Diary.class);
dateAndTitle = diary.data.date+" "+diary.data.title;
diaryBody = diary.data.body.split("。");
JSONをバインドするクラスの追加
Jersey+Jacksonでサービスを呼び出した場合に、返ってきたJSONの中身をバインドするクラスを作っておく必要がある。VstoneMagicでクラス追加するので、普通に行けるかやってみた。追加したクラスはDiaryとDiaryDataの二つ。サービス側の返し方変えれば一つで済むのと、クラス名がいけていないのはいったん気にしないでもらいたい(クラス名にDataとかつけちゃうのはよく昔怒られたパターン)。
それぞれ画像のようにコンストラクタの中にパブリックの変数としてもたせておきたいものを変数宣言ブロックで追加してあるだけ。
一つ持たせ方として普通にできるのかやってみたこととして、DiaryDataをDiaryのメンバとしてもたせるのをVstoneMagicからできるのかという点。Diaryクラスの変数dataのプロパティで型の指定をDiaryDataとして入力した。これで普通にメンバとして記述される模様。
やってみて
上記すべてやったところで、実際にSotaに送り込み動作させてみた。ビルドも問題なく通って、実際にとってきたとあるサイトの内容を喋らせることができた。
やってみて思ったのは、できるのはできるが、今回のように外部参照するライブラリが多いような実装を追加でしたい場合に、VstoneMagicだけでやるのは、インポートの補完機能などがないので、けっこう面倒くさい。そもそもツールとして担っている役割が違うのだから当たり前といえば当たり前で、例えばある程度の機能をEclipseなどの便利なIDEで実装してJarファイル化し、細かいインポートなどはそちらに隠蔽してから、そのJarを参照、自由記述処理のところにはメソッド一発呼び出すくらいにするのが妥当かなぁと。
動きをつけたりするのはEclipseでやるよりも、やはりVstoneMagicを使った方がやりやすいので、適切な使い分けができるようなノウハウを貯めていくのが吉な気がした。