チュートリアル
このチュートリアルの目的は、読者が自作のGroovyアプリケーションを書き、Google App engineにデプロイできるようにするために、手早くGaelykを使い始められるようにすることです。すでにGoogle App Engine SDKをダウンロードしてマシンにインストールしてあることを前提としています。 もしまだなら、Googleの説明を読んで準備をすませてください。
もっとも簡単で手っ取り早いセットアップの方法は、ダウンロードセクションからテンプレートプロジェクトをダウンロードすることです。これは、正しく設定ずみの設定ファイルや適切なディレクトリ構造を含んだプロジェクトのひな形を提供します:
- あらかじめGaelykサーブレット用に設定された
web.xml - 適切に設定ずみ(static-filesディレクティブ)の
appengine-web.xml - Groovletとテンプレートのサンプル
- 必要なJAR (Groovy、GaelykやGoogle App Engine SDK)
Gaelykを構成しているクラスのjavadocをブラウズすることもできます。
プロジェクトの設定
ディレクトリ構成
Gaelykのテンプレートプロジェクトで提案されている、ディレクトの構成を見て行きましょう:
/
+-- build.groovy
+-- src
+-- war
|
+-- index.gtpl
+-- css
+-- images
+-- js
+-- WEB-INF
|
+-- appengine-web.xml
+-- web.xml
+-- classes
+-- groovy
| |
| +-- controller.groovy
|
+-- includes
| |
| +-- header.gtpl
|
+-- lib
|
+-- appengine-api-1.0-sdk-x.y.z.jar
+-- appengine-api-labs-x.y.z.jar
+-- gaelyk-x.y.z.jar
+-- groovy-all-x.y.z.jar
プロジェクトのルートには、以下が含まれています:
build.groovy:srcディレクトリに含まれるGroovyとJavaソースをコンパイルするための、GroovyのAntBuilderを使った小さなGroovyビルドファイル。Groovyのジョイントコンパイラを使います。コンパイルすべき追加のソースができた場合、このビルドを実行するためには、単にgroovy buildコマンドを起動するだけです(マシンにGroovyのインストールが必要)。src: テンプレートやgroovlet以外のソースファイルが必要になった場合、このディレクトリに置くことができます(JavaでもGroovyでも)。ローカルのApp Engine開発用サーバを実行する前、またはアプリをApp Engineにデプロイする前には、このスクリプトを実行して、自作のGroovy/Javaクラスをコンパイルしておくべきです。war: このディレクトリにはApp Engineにデプロイするものが置かれます。テンプレートやイメージ、JavaScriptファイル、スタイルシートなどが含まれます。典型的Java Webアプリケーションの伝統的なWEB-INFディレクトリも含まれます。
WEB-INFディレクトリには、以下が含まれています:
appengine-web.xml: App Engine特有の設定ファイル(後述)。web.xml: Webアプリケーション用の通常のJava EE設定ファイル。classes: (build.groovyでコンパイルされた)コンパイル済みのクラスはこのディレクトリに置かれます。groovy: このフォルダには、Groovyで書いたコントローラやサービスのファイル(Groovlet形式)を置きます。includes: インクルードされるテンプレートはこのディレクトリに置くことをお勧めします。lib: 必要なすべてのライブラリはここに置かれます。Groovy、Gaelyk、GAE SDKなどのJARファイル、さらにアプリケーションに必要なその他のJARなどです。
メモ: Groovyスクリプトやインクルードは上記とは別の場所に置くこともできますが、それ以外のファイルやディレクトリについては変更できません。App Engineやサーブレットコンテナが上記の場所にあることを想定しているためです。
設定ファイル
ディレクトリ構成が整ったので、次に設定ファイルを詳しく見ていきましょう。標準的なweb.xmlと、App Engine特有のappengine-web.xmlです:
appengine-web.xml
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<!-- Your application ID -->
<application>myappid</application>
<!-- The current version of your deployed application -->
<version>1</version>
<!-- Exclude Groovlets and templates from the static files -->
<!-- to let App Engine know these files are not just mere resources -->
<static-files>
<exclude path="/WEB-INF/**.groovy" />
<exclude path="**.gtpl" />
</static-files>
</appengine-web-app>
ここで唯一変わっているところは、.groovyと.gtpl拡張子を持つファイルを除外していることです。これらのファイルは非staticであり、GaelykのGroovletとテンプレートに対応するからです。これらのファイルを、イメージやスタイルシートのような単なるリソースファイルとして扱わないように、App Engineに指示しています。
メモ: 拡張子付きのURLからどんな技術が使われているかを知られたくなければ、.groovyや.gtpl以外の拡張子を使うこともできます。
web.xml
<web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5">
<!-- The Gaelyk Groovlet servlet -->
<servlet>
<servlet-name>GroovletServlet</servlet-name>
<servlet-class>groovyx.gaelyk.GaelykServlet</servlet-class>
</servlet>
<!-- The Gaelyk template servlet -->
<servlet>
<servlet-name>TemplateServlet</servlet-name>
<servlet-class>groovyx.gaelyk.GaelykTemplateServlet</servlet-class>
</servlet>
<!-- Specify a mapping between *.groovy URLs and Groovlets -->
<servlet-mapping>
<servlet-name>GroovletServlet</servlet-name>
<url-pattern>*.groovy</url-pattern>
</servlet-mapping>
<!-- Specify a mapping between *.gtpl URLs and templates -->
<servlet-mapping>
<servlet-name>TemplateServlet</servlet-name>
<url-pattern>*.gtpl</url-pattern>
</servlet-mapping>
<!-- Define index.gtpl as a welcome file -->
<welcome-file-list>
<welcome-file>index.gtpl</welcome-file>
</welcome-file-list>
</web-app>
web.xmlでは、Groovlet用とテンプレート用の二つのGaelykサーブレットを定義し、それぞれ.groovyと.gtplで終わるURLにマッピングしています。そして、index.gtplをウェルカムファイルとして定義し、ディレクトリのように見えるURLがこのデフォルト名のファイルを探し、テンプレートとして使うようにしています。
ビューとコントローラ
さて、プロジェクトの準備が整ったので、そろそろGroovletとテンプレートの話に入りましょう。
ヒント: (一般的なMVCパターンに従って)ビューをロジックから分離するのはよいプラクティスです。Gaelykは、ビューテンプレートとGroovletコントローラの両方を提供しているので、前者をビューに、後者をロジックに使うのがオススメです。
Gaelykは、App Engine SDKのAPIへのショートカットを追加するため、Groovy自体が持つGroovletとテンプレートサーブレットの上に構築されています。
メモ: このIBM developerWorksの記事で、GroovyのGroovletとテンプレートについてさらに学ぶことができます。GaelykのGroovletとテンプレートはGroovyのものの単純な拡張です。App Engineサービスへのアクセスを加えることによって、GroovyのGroovletとテンプレートをシンプルに強化したもので、Groovyのカテゴリを使っていくつかのメソッドを追加しています。
特別なサーブレットバインディングにより、いくつかの暗黙の変数への直接アクセスが可能になっており、これらをビューやコントローラの中で使うことができます:
イーガー(事前初期化)変数
- request :
HttpServletRequestオブジェクト - response :
HttpServletResponseオブジェクト - context :
ServletContextオブジェクト - application :
contextと同じ - session :
request.getSession(false)の略記 (nullの場合もある)。HttpSessionを返す - params : 全フォームパラメータのマップ (空の場合もある)
- headers : 全requestヘッダフィールドのマップ
レイジー(遅延初期化)変数
- out :
response.getWriter()の略記。PrintWriterを返す - sout :
response.getOutputStream()の略記。ServletOutputStreamを返す - html : n
ew MarkupBuilder(response.getWriter())の略記。MarkupBuilderを返す
メモ: イーガー変数は、Groovletやテンプレートのバインディングに事前に用意されます。レイジー変数は、初めて要求されたときにインスタンスが生成され、バインディングに挿入されます。
Groovyのサーブレットバインディングが提供する上記の標準的なServlet変数に加えて、Gaelykでは、Google App Engine SDK固有の要素を注入することで独自の拡張も行っています:
- datastoreService : データストアサービス
- memcacheService : Memcacheサービス
- urlFetchService : URLフェッチサービス
- mailService : メールサービス
- imagesService : イメージサービス
- userService : ユーザサービス
- user : 現在ログインしているUser (ログインしていなければ
null) - defaultQueue : デフォルトキュー
- queues : 設定したキューにアクセスするためのマップのようなオブジェクト
- xmppService : Jabber/XMPPサービス
このような変数やサービスが使えるおかげで、短く簡潔な構文でGoogleのサービスやServlet固有の機能にアクセスでき、アプリケーションのコードをより効率的にすることができます。次のセクションでは、GaelykテンプレートとGroovletの詳細に入っていきます。
テンプレート
Gaelykのテンプレートは、JSPやPHPに非常に似たもの、つまりコード片を含んだページです。以下のことが可能です:
<% /* コード */ %>の中にGroovyコードを書く- 上記スクリプト片の中で
printやprintlnを呼んで、サーブレットのWriterに書き出す <%= 変数 %>記法を使い、出力の中に値を埋め込む- GString記法
${変数}を使い、テキストや値を埋め込む
テンプレートがどんな風になるか、実例を詳しく見てみましょう:
<html>
<body>
<p><%
def message = "Hello World!"
print message %>
</p>
<p><%= message %></p>
<p>${message}</p>
<ul>
<% 3.times { %>
<li>${message}</li>
<% } %>
</ul>
</body>
</html>
このテンプレートから生成されるHTMLは次のようになります:
<html>
<body>
<p>Hello World!</p>
<p>Hello World!</p>
<p>Hello World!</p>
<ul>
<li>Hello World!</li>
<li>Hello World!</li>
<li>Hello World!</li>
</ul>
</body>
</html>
クラスをインポートする必要があるなら、テンプレートの冒頭のスクリプト片の中で、次のコードで示すようにimportを定義することもできます:
<% import com.foo.Bar %>
メモ: もちろん、Groovyのタイプエイリアスも使えます。例えばimport com.foo.ClassWithALongName as CWALNのように。その後、def cwaln = new CWALN()のようにして、このクラスのインスタンスを生成できます。
メモ: importディレクティブがJSPディレクティブとは異なることにも注意してください(これを書いている時点では)。
前のセクションで述べたように、Servletオブジェクト(request, response, session, context)や、Google App Engine自体が持つサービスにもアクセスできます。例えば、次のテンプレートは、ユーザが現在ログインしているかどうかによって異なるメッセージを表示します:
<html>
<body>
<% if (user) { %>
<p>You are currently logged in.</p>
<% } else { %>
<p>You're not logged in.</p>
<% } %>
</body>
</html>
インクルード
異なるページ間で特定の表示要素を再利用したいことはよくあります。例えば、常に同じヘッダ、フッタ、ナビゲーションメニューなどを使う場合、インクルードの仕組みが役に立ちます。前述のように、テンプレートはWEB-INF/includesに置くことができます。メインページで、次のようにしてテンプレートをインクルードすることができます:
<% include 'WEB-INF/includes/header.gtpl' %>
<div>My main content here.</div>
<% include 'WEB-INF/includes/footer.gtpl' %>
リダイレクトとフォワード
他のテンプレートやGroovletにチェイン(連鎖)したい場合、Servletのリダイレクトやフォワード機能を使うことができます。フォワードするには、単にこうします:
<% forward 'index.gtpl' %>
リダイレクトの場合は次のようにできます:
<% redirect 'index.gtpl' %>
Groovlet
ビューテンプレートとは対照的に、Groovletは単なるGroovyスクリプトです。しかし、出力に直接書き込むため、サーブレットのOutputStreamやWriterにアクセスすることができます。また、ビューにHTMLやXMLのコンテンツを出力するために、MarkupBuilderを使うこともできます。
Groovletの実例を見てみましょう:
println """
<html>
<body>
"""
[1, 2, 3, 4].each { number -> println "<p>${number}</p>" } def now = new Date()
println """
<p>
${now}
</p>
</body>
</html>
""" ビューに、HTMLや他のテキスト形式のコンテンツを出力するためにprintやprintlnを使うことができます。printやprintlnは、System.outではなくサーブレットの出力に書き出します。HTMLやXMLの出力には、テンプレートを使うか、次のセッションで見るようにMarkupBuilderで書いた断片を送信する方がよいでしょう。こういったGroovyスクリプトの中では、Groovyのすべての機能と構文要素(リスト、マップ、制御構造、ループ、メソッド生成、ユーティリティクラス等)が使えます。
MarkupBuilderを使ったXML/HTML生成
GroovyのMarkupBuilderは、醜いprintlnを多用する必要なしに、Groovyな記法を使ってマークアップコンテンツ(HTML / XML)を生成できるユーティリティクラスです。前の例のGroovletは、次のようにもっとすっきり書けます:
html.html {
body {
[1, 2, 3, 4].each { number -> p number }
def now = new Date()
p now
}
}
メモ: MarkupBuilderについては、Groovy wikiのドキュメントや、このIBM developerWorksの記事でもっと詳しく学ぶとよいでしょう。
ビューテンプレートへの委譲
リダイレクトとフォワードのセクションで説明したように、Groovletの末尾で、制御をテンプレートにリダイレクトやフォワードすることができます。これは、ビューからロジックを適切に分離したいとき、特に有用です。例えば、前出のGroovletの改良を続けて、必要なデータを計算するGroovletと、それを表示するテンプレートに分けてみましょう。Groovletとテンプレートが一つづつ必要になります。WEB-INF/groovy/controller.groovyに置くGroovletは次のようになるでしょう:
request.setAttribute 'list', [1, 2, 3, 4]
request.setAttribute 'date', new Date()
forward 'display.gtpl'
このGroovletは、テンプレートにデータを転送する手段としてリクエスト属性を使っています。そして、Groovletの最後の行でdisplay.gtplというテンプレートビューにデータをフォワードしています:
<html>
<body>
<% request.getAttribute('list').each { number -> %>
<p>${number}</p>
<% } %>
<p>${request.getAttribute('date')}</p>
</body>
</html>
URLマッピング
Groovletやテンプレートに別の拡張子を使う
ここまでは、Groovletには.groovy、テンプレートには.gtplという拡張子を使ってきました。しかしこれらの拡張子は決して必須であるわけではありません。例えば、Groovletには.action、テンプレートには.htmlを使うことにしてもよいのです。そうすれば、どんな技術が基盤に使われているか誰も予想することはできません。これは、単にweb.xmlのサーブレットマッピングを変更するだけですみます。
...
<servlet-mapping>
<servlet-name>GroovletServlet</servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>TemplateServlet</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
...
.groovy拡張子を隠したりオプションにしたい場合、Groovletのあまり知られていない機能である、正規表現(regex)リソース置換初期パラメータを利用することもできます。Gaelykサーブレットを定義する際、web.xmlで以下の初期パラメータを定義すれば、.groovy拡張子をオプションにできます:
...
<servlet>
<servlet-name>GroovletServlet</servlet-name>
<servlet-class>groovyx.gaelyk.GaelykServlet</servlet-class>
<init-param>
<!-- This parameter is true by default, you can omit it if you want to replace all occurrences -->
<param-name>resource.name.replace.all</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>resource.name.regex</param-name>
<param-value>(\w*/)*((\w*)(\.groovy)?)(\?.*)?</param-value>
</init-param>
<init-param>
<param-name>resource.name.replacement</param-name>
<param-value>$3.groovy</param-value>
</init-param>
</servlet>
...
このメカニズムはリクエストURIをとり、そのパスを正規表現置換を使って異なるパスに変換し、別のリソース(すなわちGroovlet)に振り向けます。例えば、パスが/knowledgeBase/question/show?id=33のとき、それはshow.groovyに変換され、GroovletサーブレットがGroovletを見つけるために使われるでしょう。ここでは、起動すべきGroovletを選ぶために、マッチした最後のワードだけを使っています。$3で示される3番目のグループは、正規表現中にある二つ目の\w*に対応しています。
正規表現をちょっといじれば、パス情報を残すようにすることもできます。正規表現を((\w*/)*)((\w*)(\.groovy)?)(\?.*)? として、置換文字列を$1$3.groovyにすればよいでしょう。前の例とは対照的に、結果は/knowledgeBase/question/show.groovyになるでしょう。
メモ: 適切な正規表現と置換文字列を見つけるため、以下のGroovyコードをGroovyコンソールやシェルで実行して、マッチの検証をするとよいでしょう:
(訳注)groovyshを使う場合は各行冒頭の"String"を省いてください。
String myPath = '/knowledgeBase/question/show?id=33'
String regex = '(\\w*/)*((\\w*)(\\.groovy)?)(\\?.*)?'
String replacement = '$3.groovy'
// 初期パラメータ"resource.name.replace.all"がfalseならreplaceFirst()を、
// そうでなければreplaceAll()を使ってください
assert myPath.replaceFirst(regex, replacement) == 'show.groovy'
注意: 正規表現の(\?.*)?の部分は、URL中のリクエストパラメータに対応しています。結果の文字列にはこの部分が含まれないよう注意してください。ファイルシステム上にはshow.groovy?id=32みたいな名前のファイルはないでしょうから。
REST-fulなURL
今日では、REST型のアーキテクチャスタイルがWebアプリケーションのベストプラクティスになっています。そのため、Gaelykのページやサービスの利用者に対して、RESTfulなURLを提供したいと思う場合もあるでしょう。このセクションでは、前述の文字列置換のテクニックを使うことによって、RESTfulなURLを使えるようにする方法を見てみましょう。上で提案したものと似た、パス情報を残し.groovy拡張子を省略する手法が使えるかもしれません。また、よりシンプルなマッピングを使って、すべてのURLを一つの中央RESTエンドポイントにリダイレクトし、それを一種のフロントコントローラとして使うこともできるでしょう。これは、次のようにして設定できます:
...
<servlet>
<servlet-name>GroovletServlet</servlet-name>
<servlet-class>groovyx.gaelyk.GaelykServlet</servlet-class>
<init-param>
<param-name>resource.name.replace.all</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>resource.name.regex</param-name>
<!-- Match all request URIs (regular expression) -->
<param-value>.*</param-value>
</init-param>
<init-param>
<param-name>resource.name.replacement</param-name>
<!-- Always redirect to the rest.groovy Groovlet -->
<param-value>rest.groovy</param-value>
</init-param>
</servlet>
...
<servlet-mapping>
<servlet-name>GroovletServlet</servlet-name>
<!-- Match all request URIs (servlet url pattern matching) -->
<url-pattern>/*</url-pattern>
</servlet-mapping>
...
注意: サーブレットマッピングのURLパターンは、通常のJavaの正規表現ではないことに気をつけてください。一方、resource.name.regex初期パラメータは真のJava正規表現に対応しています。 そして、rest.groovy Groovletの中で以下を使って、使用されたHTTPメソッドを知ることができます:
(訳注:「使用されたHTTPメソッド」ではなく「リクエスト対象のリソース」だと思われます)
request.requestURI.tokenize('/')
/customer/32のようなパスに対しては、['customer', '32']のようなGroovyリストが得られるでしょう。
使われているHTTPメソッドは次のようにして取得できます:
request.method
そして、params変数を使えば、リクエストパラメータも取得可能であることを思い出してください。Google App Engine専用のショートカット
App Engineサービスへの直接アクセスの提供に加えて、GaelykはこれらのAPIの上にシンタックスシュガーも加えています。これらの改良点のいくつかを見てみましょう。
メモ: これらの追加はまだあまり多くはありませんが、将来、必要に応じてもっと増えるかもしれません。
メールサービス用の新しいsend()メソッド
Gaelykは、名前付き引数をとる新しいsend()メソッドをメールサービスに追加しています。これにより、新しいメッセージをいちいち手作業で構築する必要がなくなります。Groovletの中でメッセージを送る場合、次のように書けます:
mailService.send to: 'foobar@gmail.com',
subject: 'Hello World',
htmlBody: 'Hello'
同じように、アプリケーションの管理者宛のメール送信用にsendToAdmins()メソッドも追加されました。
低レベルデータストアAPIの改良
ビューとコントローラセクションで説明したように、Google App EngineではJDOとJPAが利用可能ですが、Gaelykではデータストアにアクセスする低レベルAPIも使えます。そして、このAPIのEntityクラスをちょっぴりGroovyフレンドリーにしています。
マップやPOJO/POGOのようにEntityを扱う
メモ: POGOとは"Plain Old Groovy Object"の略です。
JavaからEntityクラスを使う場合、EntityのプロパティへのアクセスにはsetProperty()やgetProperty()のようなメソッドを使わなければならず、コードが必要以上に冗長になってしまいます(少なくともJavaでは)。理想的には、このクラス(そしてそのインスタンス)を、あたかも単なるマップか、あるいは普通のJava Beanであるかのように扱えるようにしたいでしょう。まさにこれがGaelykが提案していることで、マップのような添字演算子や、普通のプロパティ記法が使えるようになっているのです。次の例ではEntityへのアクセス方法を示しています:
import com.google.appengine.api.datastore.Entity
Entity entity = new Entity("person")
// マップアクセスのような添字記法
entity['name'] = "Guillaume Laforge"
println entity['name']
// 普通のプロパティアクセス記法
entity.age = 32
println entity.age
Entityへのsave()およびdelete()メソッドの追加
前のサブセクションでEntityを生成しましたが、これをGoogle App Engineのデータストアに保存する必要があります。 また、データストアから取得したEntityを削除したい場合もあるでしょう。これを行うためには、古典的な方法では、DataServiceインスタンスのsave()やput()メソッドを呼ぶ必要があるでしょう。しかし、GaelykではEntityに動的にsave()やdelete()メソッドが追加されています:
def entity = new Entity("person")
entity.name = "Guillaume Laforge"
entity.age = 32
entity.save()
後になって、操作しているEntityを削除する必要がある場合は、単に次のように呼び出すことができます:
entity.delete()
データストアサービスへのwithTransaction()メソッドの追加
最後に大事なこととして、トランザクション処理が必要なとき、DataServiceのbeginTransaction()メソッドを使い、そのTransactionのcommit()やrollback()メソッドを使って自分で適切なトランザクション処理を行うかわりに、GaelykがDataServiceに追加したwithTransaction()メソッドを利用することができます。このメソッドは、あなたにかわって退屈な仕事をこなしてくれます:
datastoreService.withTransaction {
// トランザクション内でのentityに対する処理
}
withTransaction()メソッドは、唯一のパラメータとしてクロージャをとり、そのクロージャ内のコードはGaelykによってトランザクションのコンテキストで実行されます。
問い合わせ
現在、Gaelykは、データストアの問い合わせについては、Google App Engine SDKが提供している以上の追加機能は提供していません。しかし、将来のリリースではこの状況は変わる可能性があります。以下は、Groovy Web Consoleで実際に使われている、特定の作者によるスクリプトを作成日時の降順にソートして取得するための問い合わせ例です:
import com.google.appengine.api.datastore.*
import static com.google.appengine.api.datastore.FetchOptions.Builder.*
// データストアに保存されたスクリプトの問い合わせ
def query = new Query("savedscript")
// 結果を作成日時の降順にソート
query.addSort("dateCreated", Query.SortDirection.DESCENDING)
// エントリをフィルタして特定の作者によるスクリプトだけを返す
query.addFilter("author", Query.FilterOperator.EQUAL, params.author)
PreparedQuery preparedQuery = datastoreService.prepare(query)
// 結果のうち最初の10個だけを返す
def entities = preparedQuery.asList( withLimit(10) )
タスクキューAPIのショートカット
Google App Engine SDKのバージョン1.2.5では、タスクキューの実験的なサポートが追加されました。各アプリケーションは一つのデフォルトキューを持ちますが、/WEB-INFにあるqueue.xmlファイルを設定すれば、他のキューを追加することもできます。
メモ: キューやタスクキュー、それらの設定方法についてはオンラインドキュメントでもっと詳しく学ぶことができます。
Groovletやテンプレートの中では、バインディングに渡されたように、デフォルトキューに直接アクセスすることができます:
// デフォルトキューへのアクセス
defaultQueue
添字記法やプロパティアクセス記法を使ってキューにアクセスすることもできます:
// 添字記法を使って"dailyEmailQueue"という名前のキューにアクセス
queues['dailyEmailQueue']
// プロパティアクセス記法を使った場合
queues.dailyEmailQueue
// デフォルトキューには次のようにアクセスすることも可能:
queues.default
キューの名前を取得するために、用意されているgetQueueName()メソッドを呼ぶこともできますが、GaelykはQueueにgetName()メソッドを追加しているので、より冗長なqueue.getQueueName()やqueue.queueNameのかわりに、queue.nameと書くことができ、"queue"の繰り返しを避けることができます。
SDKを使って、タスクを作成したりそれをキューに加えるためには、TaskOptions.Builderを使う必要があります。このビルダーアプローチに加えて、Gaelykでは、名前つき引数を使った、キューへのタスク追加のためのショートカット記法を提供しています:
// キューにタスクを追加
queue.add countdownMillis: 1000, url: "/task/dailyEmail",
taskName: "Send daily email newsletter",
method: 'PUT', params: [date: '20090914'],
payload: content
以下はオーバーロードされた<<演算子を使ったバージョンです:
// キューにタスクを追加
queue << [
countdownMillis: 1000, url: "/task/dailyEmail",
taskName: "Send daily email newsletter",
method: 'PUT', params: [date: '20090914'],
payload: content
]
XMPP/Jabberサポート
Google App Engine SDKのバージョン1.2.5から、XMPP/Jabberに対応したインスタントメッセージングのサポートが追加されました。これによって、Gaelykアプリケーションからもインスタントメッセージの送受信ができるようになったのです。
メモ: XMPPサポートについてはオンラインドキュメントでもっと詳しく学ぶことができます。
メッセージの送信
Gaelykは、インスタントメッセージを送ったり、ユーザのプレゼンスを取得したり、他のユーザに招待を送ったりするためのいくつかの追加メソッドを提供しています。
アプリケーションは通常、アプリケーションIDにちなんで付けられた、gaelyk@appspot.comのようなJabber IDを持ちます。他のユーザにメッセージを送れるようになるためには、他のユーザを招待して、チャットに招待されなければなりません。メッセージを送信できるために、これを行っていることを確認してください。
ユーザへのメッセージ送信がGroovletの中でどんな風になるか見てみましょう:
String recipient = "someone@gmail.com"
// 対象ユーザがオンラインかチェック
if (xmppService.getPresence(recipient).isAvailable()) {
// メッセージ送信
def status = xmppService.send to: recipient, body: "Hello, how are you?"
// メッセージがすべての受信者に無事に届いたかチェック
assert status.isSuccessful()
}
Gaelykはここでもまた、App Engine SDKのさまざまなXMPP関連クラスを新しいメソッドで装飾しています:
XMPPServiceのインスタンスではSendResponse send(Map msgAttr): 詳細は後述void sendInvitation(String jabberId): ユーザに招待を送るsendInvitation(String jabberIdTo, String jabberIdFrom): 別のJabber IDからユーザに招待を送るPresence getPresence(String jabberId): 指定ユーザのプレゼンス情報を取得するPresence getPresence(String jabberIdTo, String jabberIdFrom): 同上だがリクエストには別のJabber IDを使う
MessageインスタンスではString getFrom(): このメッセージの送信者のJabber IDを取得するGPathResult getXml(): XMLペイロードをXmlSlurperでパースした結果のドキュメントを取得するList<String> getRecipients(): 受信者のJabber IDを表すStringのリストを取得する
SendResponseインスタンスではboolean isSuccessful(): すべての受信者がメッセージを受け取ったかチェックする
送信するメッセージを作成するときに使える、さまざまな属性についてもう少し詳しく説明しましょう。XMPPServiceのsend() メソッドには以下の属性を渡すことができます:
- body : メッセージの生のテキストコンテンツ
- xml : 送りたいXMLペイロードを表すクロージャ
- to : メッセージの受信者(StringまたはStringのList)
- from : 送信者のJabber IDを表すString
- type :
MessageTypeenumのインスタンス、または以下のStringのいずれか ('CHAT','ERROR','GROUPCHAT','HEADLINE','NORMAL')
メモ:bodyとxmlは排他的です。同時に両方指定することはできません。
通常のチャットメッセージのかわりに、XMLペイロードを送れることに言及しましたが、この機能はサービスやコンピュータ同士(つまり人間同士ではない)の通信にXMPP/Jabberを使いたいとき、特に興味深いものになります。テキストメッセージを送る例はすでにお見せしたので、ここではリモートサービスにXML片を送るためにxmlでクロージャをどう使えるかを示します:
String recipient = "service@gmail.com"
// サービスがオンラインかチェック
if (xmppService.getPresence(recipient).isAvailable()) {
// メッセージ送信
def status = xmppService.send to: recipient, xml: {
customers {
customer(id: 1) {
name 'Google'
}
}
}
// メッセージがサービスに無事に届いたかチェック
assert status.isSuccessful()
}
実装の詳細:xml属性に設定されたクロージャが、XMLスタンザを生成するXmlSlurperのインスタンスに渡されます 。 (訳注)XmlSlurperではなく、StreamingMarkupBuilderだと思われます。
メッセージの受信
ユーザからのメッセージを受け取ることも可能です。このためにGaelykでは、アプリケーションに届くメッセージをwebリクエストとして受信するための新しいサーブレット(GaelykXmppServlet)を導入しています。メッセージの受信を有効にするためには、下記の二つの設定が必要です:
appengine-web.xmlに新たな設定を追加するweb.xmlでこのサーブレットを定義する
まず、appengine-web.xmlに以下の要素を追加して設定してみましょう:
<inbound-services>
<service>xmpp_message</service>
</inbound-services>
そして新しいサーブレットを次のように設定します:
...
<servlet>
<servlet-name>XmppServlet</servlet-name>
<servlet-class>groovyx.gaelyk.GaelykXmppServlet</servlet-class>
</servlet>
...
<servlet-mapping>
<servlet-name>XmppServlet</servlet-name>
<url-pattern>/_ah/xmpp/message/chat/</url-pattern>
</servlet-mapping>
...
メモ: サーブレットマッピングのURLパターンは変更できません。これは、Google App Engineがアプリケーションにメッセージを送るために使うURLが固定だからです。
このXMPPサーブレットは、アプリケーションの/WEB-INF/groovyにあるxmpp.groovyという名前のGroovyスクリプトを探します。この名前も固定ですが、こちらはGaelykによる制限です。このスクリプトが、ごく普通のGroovletと同じように、/_ah/xmpp/message/chatというURLへのPOSTとして受信メッセージを処理します。通常通り、スクリプトの中ではバインディングを通じてすべての共通変数が利用可能です。また、Messageのインスタンスである、messageという新しい変数も使えます。以下のように利用することができます:
// メッセージのボディを取得
message.body
// 送信者のJabber IDを取得
message.from
// 受信者のJabber IDのリストを取得
message.recipients
// メッセージは単なる文字列ではなくXMLドキュメントか
if (message.isXml()) {
// 生のXMLを取得
message.stanza
// XmlSlurperでパース済みのドキュメントを取得
message.xml
}
Gaelykアプリケーションのデプロイと実行
ローカルでのアプリケーションの実行
Google App Engineは、Jettyで動くローカルのサーブレットコンテナを提供しているので、自作のアプリケーションをローカル実行することができます。Gaelykのテンプレートを使っているなら(そしてマシンにApp Engine SDKがインストールされていれば)、プロジェクトのルートで以下のコマンドラインを使ってアプリケーションを実行することができます:
dev_appserver.sh war
メモ: ローカルとクラウド上での実行では、いくつかの微妙な相違点があります。二者の間で振る舞いの違いがあるかもしれないので、デプロイ後には常にアプリケーションの挙動をチェックした方がよいでしょう。
クラウドへのアプリケーションのデプロイ
アプリケーションのルートで、通常のデプロイコマンドを実行するだけです:
appcfg.sh update war