まだプログラマーですが何か?

プログラマーネタ中心。たまに作成したウェブサービス関連の話も https://twitter.com/dotnsf

タグ:agent

先日紹介したこのエントリの続きです。今度はノーツデータベースの中身を取得する Web API をエージェントで作成してみます:
ノーツの Web エージェント


対象とするノーツデータベースは「ログ」にします:
2016120301


このログデータベースは Domino サーバー上に log.nsf として存在しています。個々のログ内容は Events フォームのドキュメント内の EventList フィールド内に配列の形で格納されています:
2016120303


というわけで、同データベースからログを取り出して、XML 化して出力する、という Web API(Web エージェント)を作ってみました。基本的な考え方や作り方は前回紹介したものと同様ですが、実際の処理内容だけを変えています:
  :
:
public class JavaAgent extends AgentBase{ public void NotesMain(){ try{ Session session = getSession(); AgentContext agentContext = session.getAgentContext(); // (Your code goes here) String xml = "<?xml version=\"1.0\"?>\n<eventlist>\n"; //. 直近1週間のログのみ対象とする Date dt0 = new Date(); long t = dt0.getTime(); t -= ( 7 * 24 * 60 * 60 * 1000 ); dt0.setTime( t ); //. この(エージェントのある)データベースと同じサーバー上の log.nsf を対象とする Database cdb = session.getCurrentDatabase(); String server = cdb.getServer(); Database logdb = session.getDatabase( server, "log.nsf" ); //. Events フォームの文書を取り出す DocumentCollection docs = logdb.search( "Form=\"Events\"" ); for( int i = 1; i <= docs.getCount(); i ++ ){ Document doc = docs.getNthDocument( i ); //. EventList フィールドの配列値を取り出す Vector eventlist = doc.getItemValue( "EventList" ); Enumeration event = eventlist.elements(); while( event.hasMoreElements() ){ String line = ( String )event.nextElement();      //. 日付とログ文字列に分離 int x = line.indexOf( " " ); if( x > 0 ){ String dt = line.substring( 0, x ); //. YYYY/MM/DD hh:mm:ss String msg = line.substring( x + 3 ); //. 対象日(7日以内)のログかどうかを確認 Date dt1 = new Date( dt ); if( dt1.after( dt0 ) ){ //. XML 用にサニタイズ msg = msg.replaceAll( "\n", "" ); msg = msg.replaceAll( "\r", "" ); msg = msg.replaceAll( "&", "&amp;" ); msg = msg.replaceAll( "<", "&lt;" ); msg = msg.replaceAll( ">", "&gt;" ); msg = msg.replaceAll( "\"", "&quot;" ); xml += ( "<event datetime=\"" + dt + "\">" + msg + "</event>\n" ); } } } } xml += "</eventlist>\n"; //. 画面に出力 PrintWriter pw = getAgentOutput(); pw.println( "Content-Type: text/xml" ); pw.println( "" ); pw.println( xml ); pw.close(); }catch( Exception e ){ e.printStackTrace(); } } }

↑基本的な考え方は前回紹介したものと同じです。出力内容は log.nsf の内容なので、このデータベースを取得して、対象(1週間以内)のログを探して1行ずつ XML を生成し、最後に text/xml として出力する、という内容を Java で記述しています。

※同様の Web エージェントを LotusScript で記述する場合、最後の出力部分は Print 文で代用してください。


このエージェントをブラウザから
  http://(domino サーバー名)/(DB名)/(エージェント名)?OpenAgent
という URL を指定して実行します
2016120302


Domino サーバーのログの内容が Web API で外部から取得できるようになりました。他のデータベースでも同様に応用できると思います。

IBM ノーツで Web エージェントを作り、Web API のように外部から実行してみます。

まず HTTP タスクが有効になった IBM Domino サーバーを用意し、そこにデータベースを1つ作成します。そして Domino Designer を使って Java のエージェントを作成します。作成後、基本プロパティから「トリガー」を「スケジュール」とし、その実行タイミングを「なし」に設定しておきます:
2016120101


JavaAgent.java を開き、以下の内容を記載します:
  :
  :

public class JavaAgent extends AgentBase { public void NotesMain() { try{ Session session = getSession(); AgentContext agentContext = session.getAgentContext(); // (Your code goes here) //. クエリー文字列を取得 Document dc = agentContext.getDocumentContext(); String querystring = dc.getItemValueString( "Query_String" ); //. name パラメータが指定されていたら、その値を取得 String name = "World"; //. 指定されていない場合の既定値 String[] argv = querystring.split( "&" ); for( int i = 0; i < argv.length; i ++ ){ String[] param = argv[i].split( "=" ); String fieldname = param[0].toLowerCase(); if( fieldname.equals( "name" ) ){ name = URLDecoder.decode( param[1] ); } } //. 画面に出力 PrintWriter pw = getAgentOutput(); pw.println( "Content-Type: text/plain" ); pw.println( "" ); pw.println( "Hello " + name + "!" ); pw.close(); }catch( Exception e ){ e.printStackTrace(); } } }

URL のパラメータを解析して、name=XXXX と書かれた部分があったら XXXX 部分を取り出し、"Hello XXXX!" と text/plain で表示する、という内容です(パラメータがなければ "Hello World!" と表示します)。

※余談ですが、同様の Web エージェントを Java ではなく LotusScript で記述する場合、最後の出力部分は Print 文を使ってください。


そしてこのエージェントを URL で指定して実行します。仮に DB 名が forms.nsf 、エージェント名が Hello だったとすると、
  http://(domino サーバー名)/forms.nsf/Hello?OpenAgent
と指定します:
2016120102


パラメータを指定する場合は、"&name=XXXX" を付けて実行します:
2016120103


GET でしか使えませんが、Web API っぽいのが作れました。

IBM Bluemix をはじめ、多くの便利な REST API が公開されています。REST API は HTTP プロトコルベースなので、特定のプラットフォームやプログラミング言語環境に縛られることなく、HTTP クライアント機能を持っている多くのプラットフォームやプログラミング言語から利用できるようになっています。

その一方で、IBM ノーツを使っている技術者の立場として、こういった外部の REST API をノーツからどうやって使うか、という情報があまり公開されておらず、「ノーツからは使えないんじゃないか?」と思われてしまっているように感じています。実際、ハードル高いと思う。

1点補足すると、これは「ドミノサーバー」からの話ではなく「ノーツクライアント」からの話です。ドミノサーバーであれば、Java サーブレットも使えるし、もちろんそのサーブレットからノーツデータベースにアクセスすることもできるので、連携もあまり問題ないと思うのですが、問題は「ノーツクライアント上」から「外部の」 REST API を使う、というケースです。 特に「外部の」という部分がミソで、JavaScript の AJAX などから使おうとしてもクロスサイトスクリプティング制約があったりするので、結構難しい問題だと思っています。


で、今回紹介するのは、ノーツクライアントから外部の REST API を使う方法です。"REST API" といっても、要は
 ・特定の URL に対して GET や POST でデータを送信してリクエストすると、
 ・結果が XML や JSON で返ってくるもの
という緩い定義で考えるものにします。これをノーツクライアントから実行して結果を受け取る、というものです。

今回は REST API のサンプルとして、マンホールマップの公開 API を使います。具体的には以下の URL に(GET で)アクセスすると、マンホールマップサービスに投稿されたデータから、最新20件のデータを取り出して JSON フォーマットで返す、というごく一般的(?)なものです:
http://manholemap.juge.me/searchcreated?limit=20&format=json

試しにウェブブラウザでこの URL にアクセスすると、取得結果の JSON がそのまま表示されて、こんな感じの結果になります。ちなみにこの JSON の中身の意味は・・・興味ある人はコメントなどで連絡ください、個人的に教えます(苦笑):
2015033001


この REST API にノーツクライアントからアクセスして JSON データを取得し、中身を解析してノーツで活用しよう、というのが今回の目的です。

まず最初に、これを実現するには「REST API を実行して、結果を取得する」という、ウェブ API ではごく当たり前のことを行う必要があるのですが、ノーツクライアントからこれを実行するには Java エージェントを使う必要があります。LotusScript や式関数などのマクロではノーツに関するカスタマイズ処理を記述することはできますが、TCP/IP 通信や HTTP プロトコルのレベルで処理をカスタマイズする方法は用意されていません。LSX(LotusScript eXtension) や WinSock の DLL を使って TCP/IP のソケット通信を行って・・・という手段がないわけではないと思いますが、そこまで自前で用意した上で更に HTTP プロトコルを実装、となるとさすがに非現実的です。その点、Java を使えば TCP/IP 通信は標準で持っているし、外部の便利なライブラリを使って比較的簡単に HTTP 通信を実現することもできます(更に言うと今回の例では JSON の解析も行う必要がありますが、それもライブラリに任せることができます)。 という背景もあって、今回は Java エージェントを使って REST API を実行します。

実際に Java エージェントを作る前の準備として、HTTP 通信用の Java ライブラリを用意しておきます。選択肢としてはいくつかあると思いますが、サンプルの多さなどから自分が個人的に使っているのが Jakarta Commons(Apache Commons)HTTP Client 3.1 です。この本体と、更に依存関係で必要な Logging ライブラリ、そして Codec ライブラリを全てダウンロードします。 更に実行結果の JSON テキストを取得した後の解析用に JSON ライブラリが必要になるので、これも JSON simple ライブラリをダウンロードしておきます。これらは同様の機能を持っているものであれば別のライブラリを使ってもいいのですが、以下のサンプルではこれらを使っている前提で紹介します。

以上をまとめて、以下の表のダウンロードサイトからライブラリ(のバイナリ)をダウンロードして、ダウンロードしたモジュールを展開するなどして表内右端列の4つの JAR ファイルをあらかじめ取得しておいてください。図のように同じフォルダに格納しておくと後で便利です(いずれも 2015年3月30日時点の最新バージョンです):
2015033002

ライブラリダウンロードサイト必要なファイル
HTTP Client 3.xCommons HttpClient Archivescommons-httpclient-3.1.jar
LoggingDownload Apache Commons Loggingcommons-logging-1.2.jar
CodecDownload Apache Commons Codeccommons-codec-1.10.jar
JSON simpleDownloads - JSON simplejson-simple-1.1.1.jar


では改めてノーツ上で Java エージェントを作ります。目的のデータベース(なければ新規に作って)をドミノデザイナーで開き、コード - エージェントをダブルクリックして選択後に「新規エージェント」ボタンをクリックして、エージェントを追加します:
2015033000


新規エージェント作成画面でエージェント名(図では "REST API Sample")を入力し、エージェントタイプに "Java" を選択して OK ボタンをクリックし、Java エージェントを作成します:
2015033003


新規に Java エージェントが追加されました。この中身を記述する前に、先ほど用意した4つのライブラリファイルを、このエージェント内にインポートして使えるようにしておきます。エージェント画面内の インポート - アーカイブ を選択します:
2015033004


ソースディレクトリに JAR ファイルが格納されているフォルダを指定し、インポートしたい JAR ファイルをまとめて指定して「終了」ボタンをクリックします。JAR ファイルが別々のフォルダに格納されている場合は、この手順を繰り返して全ての JAR ファイルをインポートします:
2015033005


Java エージェントのアーカイブに指定した JAR ファイルが追加されていることを確認してください。これで Java エージェント内でこれらのライブラリを使った処理を記述することができるようになりました。改めて javaAgent.java をダブルクリックして、エージェント処理の実装にとりかかります:
2015033006


ドミノデザイナー内でコードエディタが開き、javaAgent.java の編集状態になります。ここで記述された内容がこのエージェントの実行内容になります。コメントで "(Your code goes here)" と書かれた箇所の後に(というか、その前の部分を触らないように)実行内容を Java で記述します:
2015033007


今回の場合は、目的の REST API を実行して、その結果を取得する、という実装を行います。取得結果を最終的にどう扱うか(例えばノーツのデータとして格納するのか?別のデータベースに格納するのか?単に表示するだけか?など)は別に検討する必要があると思いますが、今回は結果を Java コンソールに出力する、という内容にします。なので、実装コードはこんな感じです。ここはもうノーツの世界ではなく、普通に Jakarta HTTP Client を使っているだけです:
  :
  :
  // (Your code goes here)
  HttpClient client = new HttpClient();
  GetMethod get = new GetMethod( "http://manholemap.juge.me/searchcreated?limit=20&format=json" );
  int sc = client.executeMethod( get );
  if( sc == 200 ){
    String json = get.getResponseBodyAsString();
    JSONParser parser = new JSONParser();
    JSONArray objs = ( JSONArray )parser.parse( json );
    int n = objs.size();
    for( int i = 0; i < n; i ++ ){
      JSONObject obj = ( JSONObject )objs.get( i );
      Long id = ( Long )obj.get( "id" );
      String username = ( String )obj.get( "username" );
      String text = ( String )obj.get( "text" );

      //. とりあえず整形してコンソールに出力
      String line = i + ": [" + id  + "] " + text + "(" + username + ")";
      System.out.println( line );
    }
  }
  :
  :

これで javaAgent.java およびこの(今回の例だと "REST API Sample")エージェントを保存します。


では試しにこのエージェントをノーツクライアント上で実行してみます。ノーツでこのエージェントを作ったデータベースを開きます。今回の例では最終結果を Java コンソールに出力しているので、その確認のためメニューから ツール - Java デバッグコンソールの表示 を選択します:
2015033001


こんな感じの Java コンソールウィンドウが開きます。実行結果はこの中に表示されるので、ウィンドウを閉じずにこのまま続けます:
2015033002


改めてノーツクライアントの画面に戻り、メニューから アクション - (作ったエージェント名(今回の例だと "REST API Sample")) を選択して、上記で作成した Java エージェントを実行します。実行方法はこれ以外にトリガを用意しても構いませんが、動作確認目的であれば、この方法が簡単でオススメです:
2015033003


作成した Java コードが正しく実行されると、REST API が呼び出され、結果の JSON テキストがパース&解析されて、取得したデータが1行ずつ Java コンソールに表示されるはずです:
2015033004


この例だと取得結果を Java コンソールに表示するだけでしたが、この結果を DB に格納するなり、これを元に HTML を作ってブラウザで表示する(参考記事)なり、色々な応用があると思います。ともあれノーツクライアントからであっても一般的な REST API を実行することができました。

後はこれを IBM Bluemix 上の各種 Watson API など、様々な外部 REST API を使う形で応用できると思っています。ノーツのデータを元に人工知能 API を使った解析を行うなど、いろんな使い方の可能性が広がると思っています。

 

このページのトップヘ