前回まででAtomPubの機能をすべて実装しました。今回はAtomPubの拡張仕様であるGoogle Data APIs(GData)を取り上げ、検索クエリとJSONをサポートします。GDataは、昨年秋にGoogleが発表したOpenSocialのベースになっていることで話題になりました。
全文検索にはHyper Estraierというオープンソースの検索エンジンを用います。Hyper Estraierはフレーズ検索や属性検索をサポートしているため、GDataサーバの実装にうってつけです。
サンプルコードはこちらからダウンロードできます。
GDataとは
GDataはGoogle CalendarやBloggerなどのサービスを利用するためのAPIです。AtomPubの拡張仕様として定義されています。GDataは、プロトコルに関する仕様とXMLフォーマットに関する仕様に分けられます。プロトコルでは主に検索クエリが定義されています。XMLフォーマットについては、連絡先やイベントを表現するための拡張要素が定められています。この連載はAtomPubというプロトコルを対象としているので、検索クエリを実装する方法を説明します。
なお、実際のGDataサーバはGData AuthSubなどの独自認証方式を実装しています。また、サービスごとに拡張仕様が追加されています。これらをすべて実装することはこの記事の範囲を超えます。本物のGDataサーバのコピーを作れるわけではないことをご了承ください。もちろん、OpenSocialコンテナを実装するときや、みなさんのWebサービスに検索クエリを追加するときには、この記事が役立つと思います。
こちらにGData仕様書の和訳があります。
検索クエリ
GDataでは以下のパラメータが定義されています。
GData 検索パラメータ(クエリ変数)
パラメータ |
説明 |
q |
全文検索文字列、フレーズ検索・OR検索・NOT検索をサポート |
author |
エントリの著者 |
alt |
代替表現(json,json-in-script) |
callback |
コールバック関数名(alt=json-in-scriptのときのみ有効) |
updated-min,updated-max |
エントリ更新日時の範囲 |
published-min,published-max |
エントリ発行日時の範囲 |
start-index |
取得した結果のうち、ひとつめの結果の通し番号(1から数える) |
max-results |
取得する結果の最大数 |
全文検索(q)では、フレーズ検索やOR検索、NOT検索をサポートします。フレーズ検索は、複数の連続するキーワードを検索するときに用います。q="Atom Publishing Protocol"のようにキーワードを引用符で囲みます。OR検索は、q=Atom OR RSSのように指定し、AtomあるいはRSSのいずれかが存在する文書を検索します。NOT検索はq=dog -catのように指定し、dogは含むがcatを含まない文書を検索することができます。
著者検索(author)では、エントリのauthorタグにあるname要素とemail要素を検索します。
代替表現(alt)を指定すると、Atom以外のフォーマットでレスポンスを返します。今回はalt=jsonとalt=json-in-script(JSONP)を実装します。alt=json-in-scriptのときは、コールバック関数名callbackを必ず指定しなければなりません。
updated-minなどのパラメータを使って、updated要素とpublished要素の範囲を指定することができます。Atomと同様に、RFC3339記載のフォーマットに従います。たとえば、updated-min=2008-01-01T00:00:00+09:00のようにします。
クエリの例を示します。日本時間で2008年1月1日以降のatompubを含むエントリを検索する場合は、次のようなクエリになります。
カテゴリを検索するときは、クエリ変数ではなくパスを使います。
GData検索パラメータ(パス)
パラメータ |
説明 |
/-/category |
カテゴリフィルタ |
メンバリソースのURIと区別するために、/-/で区切ってからカテゴリを指定します。OR検索は"|"で区切り、AND検索は"/"で区切ります。
たとえば、perlあるいはrubyというカテゴリを指定するときは次のようにします。
JSONレスポンス
代替表現パラメータ(alt)が指定されたときは、XMLをJSONに変換します。GDataでは、フィードをJSONに変換する方法を次のように定めています。
属性は文字列プロパティに変換
子要素はオブジェクトプロパティに変換(テキスト要素のキーは"$t")
複数存在する要素は配列プロパティに変換
Catalysltアプリケーションの作成
新しいWebアプリケーションとして実装します。GDataというWebアプリケーション(のひな形)を作ります。
Hyper Estraier のセットアップ
まず、こちらなどを参考にしてHyper Estraierをインストールしてください。
Hyper Estraierでは、文書の本文に加えて、属性を指定した検索が可能です。今回はGDataに合わせて以下の属性を用います。なお、xml属性にはエントリ本体を保存しておきます。
Hyper Estraierの属性カラム
属性名 |
説明 |
uri |
リソースのURI |
category |
カテゴリ |
author |
著者名と電子メールアドレス |
updated |
updated要素のUNIX time |
published |
published要素のUNIX time |
xml |
エントリの文字列 |
Hyper Estraierのデータベースを作成し、起動します。データベース名はtest.dbとします。
Hyper Estraierでは「ノード」と呼ばれる一種のサーバに対して検索を行います。ここで、ノードを作成します。まず、ブラウザでhttp://localhost:1978/master_uiにアクセスし(デフォルトのユーザ名とパスワードはadmin,adminです)、Manage Nodesをクリックしてください。テキストボックスが2つあるので、それぞれ"entries","エントリコレクション"と入力し、createボタンをクリックします。作成したノードのURIはhttp://localhost:1978/node/entriesとなります。
次に設定ファイルを修正しますが、その前にHyper Estraierを終了します。
設定ファイルを修正します。設定ファイルはtest.db/_confです。
設定ファイルからattrindexという文字列を検索し、上のように修正してください。attrindexは、属性インデックスの指定です。GDataでは、著者やカテゴリ、日時に対するクエリが定義されています。それらの属性に文字列型あるいは数値型のインデックスを張ります。
設定ファイルの編集が終わったら、再度Hyper Estraierを起動してください。
モデルの作成
モデルを実装します。モデルには、Catalyst::Model::Estraierを利用します。ヘルパスクリプトを使ってひな形を作成します。
コマンドの引数は次の通りです。
モデル作成スクリプトの引数
引数 |
説明 |
model |
作成対象 |
Entries |
作成するクラス名(フルパスはGData::Model::Entries) |
Estraier |
スーパークラス名(フルパスはCatalyst::Model::Estraier) |
http://localhost:1978/node/entries |
Hyper EstraierノードのURI |
admin |
Hyper Estraierのユーザ名 |
admin |
Hyper Estraierのパスワード |
コレクションコントローラの実装
ヘルパスクリプトを使ってコントローラクラスのひな形を作成します。
第1回、第2回と同様に、リソース操作(List+CRUD)を実装します。
メンバの列挙(List)
まず、エントリの列挙です。検索クエリの解析を行うため、メソッドが少し長いです。分割して説明します。
まず、検索条件オブジェクトを構築します。以降、このオブジェクトに検索条件を追加していきます。
検索クエリに簡便書式を指定します。簡便書式についてはHyperEstraierのドキュメントを参照してください。
デフォルトの検索条件として「updatedが0以上」を指定しておきます。これは、Hyper Estraierでは少なくとも1つの検索条件がひつようだからです。なお、Hyper Estraierで属性フィールドを表すときには、頭に"@"を付けます。
更新日時の新しい順にソートします。厳密には、updated要素ではなくapp:edited要素でソートすべきなのですが、今回は属性数を減らすためにupdatedで代用することにします。
全文検索クエリを解析します。GDataは"OR"という文字列でOR検索を指定しますが、Hyper Estraierではパイプ"|"で区切ります。また、NOT検索については、GDataが単語前にマイナス"-"を付けるのに対し、HyperEstraierはエクスクラメーション"!"を付けます。検索文字列を置換し、Hyper Estraierの書式に合わせておきます。
カテゴリ検索クエリを解析します。カテゴリはパスで指定されるので、$c->req->argsとしてパス部分を取得します。クエリに"|"が含まれるときはOR検索とし、含まれないときはAND検索とします。ISTRORとISTRANDはHyper Estraierの属性検索式で、それぞれOR検索とAND検索を意味します。頭の"I"は、大文字小文字を区別しないという意味です。
著者クエリを解析します。AND検索(ISTRAND)のみとします。
日時クエリを解析します。NUMGEとNUMLTは数値比較を行うHyperEstraierの属性検索式で、それぞれ「以上」「より小さい」を表します。たとえば、updated-min=2008-01-01T00:00:00:+09:00というクエリは、"@updated NUMGE 2008-01-01T00:00:00:+09:00"に変換されます。
取得結果の開始位置と最大数を解析します。GDataはエントリを1から数えますが、Hyper Estraierは0から数えます。そのため、start-indexの値から1を引いています。
検索を実行します。searchメソッドの第2引数は、複数のHyper Estraierノードを用いるときの検索深さです。
フィードのひな型を取得し、エントリを追加します。エントリはxml属性から取得します。
最後にtrueを返して終了です。
なお、フィードのページングは省略しました。第3回を参考に実装してみてください。
メンバの追加と更新(Create,Update)
エントリの追加(Create)と更新(Update)をひとつのメソッドにまとめて実装します。メソッド属性にcreateとupdateを並べると、両方のタイミングでメソッドが呼ばれるようになります。
Hyper Estraierは属性に改行文字を格納できないため、除去しておきます。
category要素やauthorの子要素は複数存在しうるので、スペースで区切ってひとつの文字列にしておきます。
文書オブジェクトに属性と本文を追加し、データベースに追加します。Hyper Estraierでは、指定されたURIを持った文書が存在すれば上書きされ、存在しなければ新たに追加されます。URIの重複対策については第3回を参考にしてください。
メンバの取得(Read)
列挙と同じように検索オブジェクトを構築し、検索条件を設定します。このメソッドでは、URIが一致するエントリを検索します。
メンバの削除(Delete)
URI を指定して文書を削除するメソッドを呼び、文書を削除します。
代替表現への変換
代替表現(alt)クエリが指定されたときには、XMLをJSONに変換します。ビュー(MVCのV)を作成してそこで変換するのがスジかもしれませんが、簡単のためにendメソッドで対応することにします。endメソッドは通常のアクションの後に呼ばれます(第3回に登場したautoの逆です)。
JSONへの変換にはGoogle::Data::JSONモジュールを使います。
クエリ変数に合わせて、HTTPレスポンス($c->res->body)を変換し、再設定します。
検索してみる
検索する前に、第2回を参考にいくつかエントリを追加してください。
適当なキーワードで検索してみましょう。たとえば、dogというキーワードで検索するにはhttp://localhost:3000/entrycollection?q=dogというURLです。正しく検索できましたでしょうか?
GDataとの相違点
今回は簡単のために、GData仕様のうちいくつか無視した点があります。
代替表現(alt)では、RSS2.0を指定することができます。しかし、AtomがダメでRSSがOKという状況はほとんどないと思いますので、実装しませんでした。
GDataでは、フィードにOpenSearch拡張を含めることとなっています。OpenSearchは、ヒット数などを示すための拡張要素です。XML::Atom::Ext::OpenSearchモジュールを使うと簡単に対応することができます。
実際のGDataでは形態素解析を行って単語単位で検索していると思われます。一方、Hyper EstraierはN-gramを基本としているため、単語の区切りを意識しません。このため、「京都」で検索して「東京都」がヒットするようなことがあります。
GDataでは、AtomPubとは異なるバージョンチェック方式が採用されています。しかし、今後はAtomPubに準拠するとのアナウンスがあったことから、この連載では紹介しませんでした。
まとめ
Catalyst::Controller::AtomPubは、AtomPubサーバの開発をサポートするCatalystコントローラです。AtomPubの複雑な側面を隠蔽し、プログラマがリソースの操作に集中できるようにします。また、AtomPubの接続実験に積極的に参加し、現在では多くのAtomPubクライアントと接続できることが確認されています。
しかし、フィードページングのように、プログラマから隠蔽し切れていない部分もあります。また、現在策定中の拡張仕様には対応できていません。今後はこれらに対応するとともに、さらに品質を高めていきたいと考えています。
本連載に最後までお付き合いいただき、ありがとうござました。Catalyst::Controller::AtomPubについての理解が深まり、皆さんが立ちあげているサービスや手がけているプロジェクトで採用していただけたら幸いです。