IBM®
本文へジャンプ
    Japan [変更]    ご利用条件
 
 
検索範囲検索:    
    ホーム    製品    サービス & ソリューション    サポート & ダウンロード    マイアカウント    
skip to main content

developerWorks Japan  >  Java technology | Web development | Open source  >

Grails をマスターする: ファイルのアップロードと Atom の配信

Grails でデータを出し入れする

developerWorks
ページオプション

JavaScript を要するドキュメントオプションは表示されません

原文はこちら

原文はこちら


レベル: 初級

Scott Davis, Founder, ThirstyHead.com

2009年 6月 09日

連載「Grails をマスターする」では今回、Scott Davis が Grails アプリケーションにファイルをアップロードする方法、そして Atom 配信フィードをセットアップする方法を紹介します。ファイルをアップロードする機能と Atom の配信機能が揃えば、Blogito は本格的なブログ・サーバーに仕上がります。

連載「Grails をマスターする」ではこれまで何回かにわたり、ごく簡易的なブログ・サービス (Blogito) を段階的に組み立ててきましたが、この記事で Blogito はついにその運命を全うし、機能的なブログ・アプリケーションになります。そのために、今回はブログ・エントリー本体のファイルをアップロードする機能を実装し、配信するための手製の Atom フィードを組み立てます。

この連載について

Grails は、Spring や Hibernate などのよく知られた Java 技術に「Convention over Configuration (設定より規約)」といった現代のプラクティスを盛り込んだ最新の Web 開発フレームワークです。Groovy で作成された Grails は既存の Java コードをシームレスに統合するだけでなく、スクリプト言語ならではの柔軟性と動的機能を与えてくれます。Grails を学んだら、Web 開発に対する今までの見方がまったく違ってくるはずです。

作業を始めるにあたり、前回の記事 (「認証と許可」) で追加した認証によって油断のならないバグが UI に入り込んでいるので、新しい機能を追加する前に、まずこのバグを修正するほうが先決です。

隠れたバグの修正

Grails を起動すると、grails-app/conf/Bootstrap.groovy によって 2 人のユーザーと 4 つの新規ブログ・エントリーが追加されます。しかしブログ・エントリーを Web インターフェースによって追加しようとすると、どうなるでしょうか。それを知るには以下のステップを試してみてください。

  1. ユーザー名には jsmith、パスワードには wordpass と入力してログインします。
  2. New Entry をクリックします。
  3. タイトルと要約を追加します。
  4. Create をクリックします。

残念なことに、以上のステップを実行すると Property [author] of class [class Entry] cannot be null というエラーで迎えられます。ブートストラップ・コードは引き続き正しく機能しているというのに、このバグは一体どうやってアプリケーションに入り込んだのでしょうか。

Blogito に着手した記事 (「Grails アプリケーションの見栄えを良くする」) では、grails generate-views Entry と入力して GSP (Groovy Server Pages) ビューを生成しました。その後の記事ではドメイン・クラスに変更を加えたものの、GSP ビューを生成し直すようには一切指示していません。しかし、リスト 1 に記載するように EntryUser 間の 1 対多の関係を追加しているため、create.gsp ビューは古いものになっています (belongsTo は、User タイプの author という名前のフィールドを作成することを思い出してください)。


リスト 1. GSP を壊した 1 対多の関係

class Entry {
  static belongsTo = [author:User]

  String title
  String summary
  Date dateCreated
  Date lastUpdated
}

耳に痛い注意事項ですが、すべてを同期した状態に保つには、ドメイン・モデルが変わりやすい開発の初期段階では尚更のこと、ビューを動的に scaffold するのが最も安全な方法です。もちろん scaffold したビューだけに頼ることはできませんが、GSP をディスク上で生成した時点で、これらのビューを最新の状態に保つ責任は Grails から開発者に移ります。

現時点でビューを Entry クラスに対して生成したとすると、Grails は Author の一覧を表示するコンボ・ボックスを提供することになります (リスト 2 を参照)。皆さんはこの作業を行わないでください。これはポイントを説明するためだけのリストです。この後すぐに、いくつかの選択肢を提供します。


リスト 2. 1 対多の関係に対して生成されるコンボ・ボックス

<g:form action="save" method="post" >
  <div class="dialog">
    <table>
      <tbody>
        <!-- SNIP -->
        <tr class="prop">
          <td valign="top" class="name">
            <label for="author">Author:</label>
          </td>
          <td valign="top"
              class="value ${hasErrors(bean:entryInstance,
                                       field:'author','errors')}">
            <g:select optionKey="id"
                      from="${User.list()}"
                      name="author.id"
                      value="${entryInstance?.author?.id}" ></g:select>
          </td>
        </tr>
        <!-- SNIP -->
      </tbody>
    </table>
  </div>
</g:form>

<g:select> 要素に注目してください。このフィールドの名前は author.id となっています。「GORM: おかしな名前の真面目な技術」で学んだように、Author の一覧に表示されるテキストは User.toString() メソッドによって提供されます。このテキストはまた、フォームが送信されるとサーバーに送られるフィールド値でもあります。この例の場合は、optionKey 属性のフィールド値を上書きして、Authorid を送ることになります (<g:select> タグについての詳細は、「参考文献」を参照してください)。

EntryController.groovy が探している author.id フィールドを渡すのに手っ取り早い方法は、リスト 3 のように隠しフィールドをフォームに追加することです。create アクションにアクセスするにはログインしている必要があること、そしてログインした User がブログ・エントリーの author であることから、安全に session.user.id を値として使用することができます。


リスト 3. フォームから author.id フィールドを渡す

<g:form action="save" method="post" >
  <input type="hidden" name="author.id" value="${session.user.id}" />
  <!-- SNIP -->
</g:form>

Blogito のような単純なアプリケーションにはこれで十分だと思いますが、この方法ではクライアント・サイドのハッカーに、author.id に別の値を注入する機会を与えてしまいます。完全に安全を確保するには、代わりに save クロージャーに Entry.author を追加するという方法を使うことができます (リスト 4 を参照)。


リスト 4. サーバーで author.id を保存する

def save = {
    def entryInstance = new Entry(params)
    entryInstance.author = User.get(session.user.id)
    if(!entryInstance.hasErrors() && entryInstance.save()) {
        flash.message = "Entry ${entryInstance.id} created"
        redirect(action:show,id:entryInstance.id)
    }
    else {
        render(view:'create',model:[entryInstance:entryInstance])
    }
}

上記ではコントローラーを生成すると作成される標準の save クロージャーに、1 行のカスタム・コードを追加しています。その行とは、session.user.id の値に応じてデータベースから User を取得し、Entry.author フィールドに入力する entryInstance.author の行です。

次のセクションでは、ファイルのアップロードを処理するように save クロージャーをカスタマイズするので、万全の安全を期して、リスト 4 のコードを EntryController.groovy に追加してください。その上で Grails を再起動し、HTML フォームを使って問題なく新しい Entry を追加できることを確認します。




上に戻る


ファイルのアップロード

Entry の作成が再び上手く機能するようになったところで、今度は別の機能を追加します。ここで目的とするのは、ユーザーが新しい Entry を作成する際にファイルをアップロードできるようにすることです。ファイルはブログ・エントリーがまるごと含まれる HTML である場合も、画像やその他のファイルである場合も考えられます。この目的を達成するためには、Entry ドメイン・クラス、EntryController、そして GSP ビューに手を加え、さらに新しい TagLib を構成に追加する必要があります。

まずは、grails-app/views/entry/create.gsp を見てください。ここに、ファイルをアップロードするための新規フィールドを追加します (リスト 5 を参照)。


リスト 5. ファイル・アップロード・フィールドを追加する

<g:uploadForm action="save" method="post" >
  <!-- SNIP -->
  <tr class="prop">
    <td valign="top" class="name">
      <label for="payload">File:</label>
    </td>
    <td valign="top">
      <input type="file" id="payload" name="payload"/>
    </td>
  </tr>
</g:uploadForm>

<g:form> タグが <g:uploadForm> に変更されていることに注目してください。このように変更することで、HTML フォームからファイルをアップロードできるようになります。あるいは、<g:form> タグをそのまま残し、enctype="multipart/form-data" 属性を追加するだけにする方法もあります (HTML フォームのデフォルト enctype は、application/x-www-form-urlencoded です)。

フォームの enctype が正しく設定されると (または <g:uploadForm> を使用する場合には)、<input type="file" /> フィールドを追加することが可能になります。これによって、ユーザーにはローカル・ファイルシステムを参照し、アップロード対象のファイルを選択するためのボタンが提供されます (図 1 を参照)。私のサンプルでは Grails ロゴを使っていますが、お好みに合わせて、どの画像ファイルでも使用することができます。


図 1. ファイル・アップロード・フィールドが追加された Create Entry フォーム

クライアント・サイドのフォームはこれで用意できたので、次はアップロードされたファイルを役立てられるように、サーバー・サイドのコードを調整します。テキスト・エディターで grails-app/controllers/EntryController.groovy を開いて、リスト 6 のコードを save クロージャーに追加してください。


リスト 6. アップロードされたファイルに関する情報を表示する

def save = {
    def entryInstance = new Entry(params)
    entryInstance.author = User.get(session.user.id)

    //handle uploaded file
    def uploadedFile = request.getFile('payload')
    if(!uploadedFile.empty){
      println "Class: ${uploadedFile.class}"
      println "Name: ${uploadedFile.name}"
      println "OriginalFileName: ${uploadedFile.originalFilename}"
      println "Size: ${uploadedFile.size}"
      println "ContentType: ${uploadedFile.contentType}"
    }

    if(!entryInstance.hasErrors() && entryInstance.save()) {
        flash.message = "Entry ${entryInstance.id} created"
        redirect(action:show,id:entryInstance.id)
    }
    else {
        render(view:'create',model:[entryInstance:entryInstance])
    }
}

上記のリストで注目すべき点は request.getFile() メソッドを使用して、アップロードされたファイルへの参照を取得していることです。アップロードされたファイルを参照できれば、ファイル上であらゆる類のイントロスペクションを実行することができます。リスト 7 に、Grails ロゴがアップロードされた後のコンソール出力を記載します。


リスト 7. ファイルのアップロード後のコンソール出力

Class: class org.springframework.web.multipart.commons.CommonsMultipartFile
Name: payload
OriginalFileName: Grails_logo.jpg
Size: 8065
ContentType: image/jpeg

Grails 内部では Spring MVC フレームワークが使用されているという知識があれば、アップロードされたファイルはコントローラーが使えるように、CommonsMultipartFile オブジェクトとなるのは意外なことではありません。このクラスは HTML フォームのフィールド名を公開するだけでなく、元のファイル名、バイト単位でのサイズ、そしてファイルの MIME タイプにもアクセスできるようにします。

次のステップは、アップロードされたファイルをどこかに保存することです。それにはリスト 8 に記載する数行のコードを save クロージャーに追加します。


リスト 8. アップロードされたファイルを保存する

def save = {
    def entryInstance = new Entry(params)
    entryInstance.author = User.get(session.user.id)

    //handle uploaded file
    def uploadedFile = request.getFile('payload')
    if(!uploadedFile.empty){
      println "Class: ${uploadedFile.class}"
      println "Name: ${uploadedFile.name}"
      println "OriginalFileName: ${uploadedFile.originalFilename}"
      println "Size: ${uploadedFile.size}"
      println "ContentType: ${uploadedFile.contentType}"

      def webRootDir = servletContext.getRealPath("/")
      def userDir = new File(webRootDir, "/payload/${session.user.login}")
      userDir.mkdirs()
      uploadedFile.transferTo( new File( userDir, uploadedFile.originalFilename))
    }

    if(!entryInstance.hasErrors() && entryInstance.save()) {
        flash.message = "Entry ${entryInstance.id} created"
        redirect(action:show,id:entryInstance.id)
    }
    else {
        render(view:'create',model:[entryInstance:entryInstance])
    }
}

Web ルートの下に payload/jsmith ディレクトリーを作成すれば、後は uploadedFile.transferTo() メソッドによってファイルを保存できるようになります。File.mkdirs() は非破壊的メソッドなので、たとえディレクトリーがすでに存在していたとしても、既存のファイルが失われることを心配せずに何度でも呼び出すことができます。

次は、filename を格納するための String フィールドを Entry クラスに追加します (リスト 9 を参照)。ここで重要なのは、この新規フィールドの値として blank (HTML フォーム内) と nullable (データベース内) のどちらも許可する制約を追加することです。


リスト 9. ファイル名フィールドを Entry に追加する

class Entry {
  static constraints = {
    title()
    summary(maxSize:1000)
    filename(blank:true, nullable:true)
    dateCreated()
    lastUpdated()
  }

  static mapping = {
    sort "lastUpdated":"desc"
  }

  static belongsTo = [author:User]

  String title
  String summary
  String filename
  Date dateCreated
  Date lastUpdated
}

そして最後に save クロージャーで、filenameEntry オブジェクトに追加します。リスト 10 に完成した save クロージャーを記載します。


リスト 10. ファイル名を Entry に格納する

def save = {
    def entryInstance = new Entry(params)
    entryInstance.author = User.get(session.user.id)

    //handle uploaded file
    def uploadedFile = request.getFile('payload')
    if(!uploadedFile.empty){
      println "Class: ${uploadedFile.class}"
      println "Name: ${uploadedFile.name}"
      println "OriginalFileName: ${uploadedFile.originalFilename}"
      println "Size: ${uploadedFile.size}"
      println "ContentType: ${uploadedFile.contentType}"

      def webRootDir = servletContext.getRealPath("/")
      def userDir = new File(webRootDir, "/payload/${session.user.login}")
      userDir.mkdirs()
      uploadedFile.transferTo( new File( userDir, uploadedFile.originalFilename))
      entryInstance.filename = uploadedFile.originalFilename
    }

    if(!entryInstance.hasErrors() && entryInstance.save()) {
        flash.message = "Entry ${entryInstance.id} created"
        redirect(action:show,id:entryInstance.id)
    }
    else {
        render(view:'create',model:[entryInstance:entryInstance])
    }
}

アップロードされたファイルをファイルシステムに保存するには、データベースにファイルを直接格納するという方法もあります。Entry 内に payload という名前の byte[] フィールドを作成すれば、上記の手順で save クロージャーに追加したカスタム・コードは一切必要なくなります。けれどもその場合には、次のセクションでのお楽しみも、まるごと逃してしまうことになります。




上に戻る


アップロードされたファイルの表示

アップロードされたファイルをどこかに表示するのでなければ、ファイルをアップロードする意味はありません。そこで、grails-app/views/entry/_entry.gsp を開いて、リスト 11 のコードを追加します。


リスト 11. アップロードされた画像を表示する GSP コード

<div class="entry">
  <span class="entry-date">
      <g:longDate>${entryInstance.lastUpdated}</g:longDate> : ${entryInstance.author}
  </span>
  <h2><g:link action="show" id="${entryInstance.id}">${entryInstance.title}</g:link></h2>
  <p>${entryInstance.summary}</p>

  <g:if test="${entryInstance.filename}">
    <p>
      <img src="${createLinkTo(dir:'payload/'+entryInstance.author.login,
                               file:''+entryInstance.filename)}"
           alt="${entryInstance.filename}"
           title="${entryInstance.filename}" />
    </p>
  </g:if>
</div>

出力を <g:if> ブロック内にラップしていますが、その理由は、ファイルのアップロードはオプションであるためです。entryInstance.filename フィールドに値が入力された場合は、<img> タグ内の結果が表示されます。

図 2 の新たなリストは、アップロードされた Grails ロゴを誇らしげに表示しています。


図 2. アップロードされた画像の表示

もしユーザーが、画像以外のファイルをアップロードする場合にはどうすればよいのでしょうか。それには、GSP にさらにロジックを追加するよりも、カスタム TagLib を作成するのが最適な方法になりそうです。




上に戻る


TagLib の作成

Blogito には、grails-app/taglib のなかに既に DateTagLib.groovy と LoginTagLib.groovy という 2 つの TagLib、があります。1 つの TagLib に定義できるカスタム・タグの数には制限がありませんが、タグをセマンティックによってグループに分類しておくために、今回は新しい TagLib を作成することをお勧めします。コマンド・プロンプトで grails create-tag-lib Entry と入力し、リスト 12 のコードを追加してください。


リスト 12. displayFile タグを作成する

class EntryTagLib {

  def displayFile = {attrs, body->
    def user = attrs["user"]
    def filename = attrs["filename"]

    if(filename){
      def extension = filename.split("\\.")[-1]
      def userDir = "payload/${user}"

      switch(extension.toUpperCase()){
        case ["JPG", "PNG", "GIF"]:
             def html = """
             <p>
               <img src="${createLinkTo(dir:''+userDir,
                                        file:''+filename)}"
                    alt="${filename}"
                    title="${filename}" />
             </p>
             """

             out << html
             break

        case "HTML":
             out << "p>html</p>"
             break
        default:
             out << "<p>file</p>"
             break
      }
    }else{
      out << "<!-- no file -->"
    }
  }

}

この後すぐにわかるように、このコードが作成する <g:displayFile> タグは、userfilename という 2 つの属性を想定しています。filename 属性に値が入力されている場合は、その値からファイル拡張子が削除され、値の残りの部分は大文字に変換されます。

Groovy の switch 文は、Java でこれに相当する文よりも遙かに柔軟性があります。第一に、String に対して switch 文を適用することが可能です (Java 言語では int に対してしか適用できません)。さらに見事なことに、case では単一の条件だけでなく条件の List を指定することもできます。

この TagLib を配置すると、部分テンプレート _entry.gsp は大幅に単純になります (リスト 13 を参照)。


リスト 13. 単純化された部分テンプレート

<div class="entry">
  <span class="entry-date">
      <g:longDate>${entryInstance.lastUpdated}</g:longDate> : ${entryInstance.author}
  </span>
  <h2><g:link action="show" id="${entryInstance.id}">${entryInstance.title}</g:link></h2>
  <p>${entryInstance.summary}</p>

  <g:displayFile filename="${entryInstance.filename}"
                 user="${entryInstance.author.login}" />

</div>

Grails を再起動して、Grails ロゴをもう一度アップロードしてください。他のファイル・タイプのサポートを追加する前に、TagLib のリファクタリングによって既存の機能が壊れていないことを確認しなければならないためです。

画像のアップロードが機能することを確認できたところで、後は switch ブロックにそれぞれのファイル・タイプに該当する case を実装すれば、他のファイル・タイプのサポートを追加することができます。リスト 14 に、アップロードされた HTML ファイルを扱う方法、そしてデフォルトの場合に単にファイルのダウンロード・リンクを作成する方法を示します。


リスト 14. 完全な switch/case ブロック

class EntryTagLib {

  def displayFile = {attrs, body->
    def user = attrs["user"]
    def filename = attrs["filename"]

    if(filename){
      def extension = filename.split("\\.")[-1]
      def userDir = "payload/${user}"

      switch(extension.toUpperCase()){
        case ["JPG", "PNG", "GIF"]:
             //SNIP
             break

        case "HTML":
             def webRootDir = servletContext.getRealPath("/")
             out << new File(webRootDir+"/"+userDir, filename).text
             break
        default:
             def html = """
             <p>
               <a href="${createLinkTo(dir:''+userDir,
                                       file:''+filename)}">${filename}</a>
             </p>
             """
             out << html
             break
      }
    }else{
      out << "<!-- no file -->"
    }
  }

}

この新しい振る舞いを適用するために、2 つのテキスト・ファイルを新規に作成します。一方のファイル名は test.html、もう一方は noextension です。リスト 15 の内容を追加した上でファイルをアップロードして、TagLib がそれぞれのコンテンツを正常に表示することを確認してください。


リスト 15. アップロードする 2 つのサンプル・ファイル

//test.html
<p>
This is some <b>test</b> HTML.
</p>

<p>
Here is a link to the <a href="http://grails.org">Grails</a> homepage.
</p>

<p>
And here is a link to the 
<img src="http://grails.org/images/grails-logo.png">Grails Logo</img>.
</p>



//noextension
This file doesn't have an extension.

Web ブラウザーの表示は図 3 のようになるはずです。


図 3. アップロードされた 3 種類のファイルの表示




上に戻る


Atom フィードの追加

ここまでの時点で、はっきりとしたパターンが見えてきたはずです。それは、Grails アプリケーションに新しい機能を追加するごとに、ほぼ間違いなくモデル、ビュー、コントローラーに手を加えることになるというパターンです。また、その過程で部分テンプレートや TagLib をおまけで追加することもあります。

Blogito に Atom フィードを追加する場合も、このパターンに従います。モデルを変更する必要はありませんが、最終的にはそれ以外のすべての作業を行うことになります。具体的には、以下の作業があります。

  1. Atom リクエストを処理するクロージャーを Entry コントローラーに追加する。
  2. 新規の GSP ページを作成し、結果を整形式 Atom 文書としてレンダリングする。
  3. 新しい部分テンプレートと新しいカスタム・タグを作成して事が上手く運ぶようにする。

Grails アプリケーションに RSS と Atom の機能を追加する便利な Feeds プラグインをインストールすることもできますが (「参考文献」を参照)、Atom フォーマットは単純なので、自力でも十分に対処できるとわかるはずです。その証拠として、既存の Atom フィードのソースを見てみるか、Atom に関するウィキペディアのページに載っているサンプルを調べてみてください (「参考文献」を参照)。さらに、Atom フォーマットの IETF 仕様である RFC 4287 を読んでもよいかもしれません (「参考文献」を参照)。あるいはこの記事を読み進めて、Grails 特有のソリューションを見るだけでも十分です。

まず始めに、atom クロージャーを EntryController.groovy に追加します (リスト 16 を参照)。


リスト 16. atom クロージャーを EntryController.groovy に追加する

def atom = {
  if(!params.max) params.max = 10
  def list = Entry.list( params )
  def lastUpdated = list[0].lastUpdated
  [ entryInstanceList:list, lastUpdated:lastUpdated ]
}

上記のクロージャーと標準の list クロージャーとの唯一の違いは、lastUpdated フィールドが追加されていることだけです。リストはすでに lastUpdated を基準にソートされているため (これは、Entry ドメイン・クラスの static mapping ブロックに含まれる sort "lastUpdated":"desc" 設定のおかげです)、リスト内の最初の Entry からこのフィールドを取得するということは、事実上、最新の日付のエントリーを取得するということになります。

次に、grails-app/views/entry/atom.gsp を作成し、リスト 17 のコードを追加します。


リスト 17. atom.gsp

<% response.setContentType("application/atom+xml") 
%><feed xmlns="http://www.w3.org/2005/Atom">
  <title type="text">News from Blogito.org</title>
  <link rel="alternate" type="text/html" href="http://blogito.org/"/>
  <link rel="self" type="application/atom+xml" href="http://blogito.org/entry/atom" />
  <updated><g:atomDate>${lastUpdated}</g:atomDate></updated>
  <author><name>Blogito.org</name></author>
  <id>tag:blogito.org,2009-01-01:entry/atom</id>
  <generator uri="http://blogito.org" version="0.1">Hand-rolled Grails code</generator>

  <g:each in="${entryInstanceList}" status="i" var="entryInstance">
<g:render template="atomEntry" bean="${entryInstance}" var="entryInstance" />
  </g:each>

</feed>

ご覧のように、まずは MIME タイプを application/atom+xml に設定します。その上で、フィードに関する基本的なメタデータとして updatedauthorgenerator などを指定します。フィード全体で blogito.org をハードコーディングしなくても済むようにしたい場合は、atom クロージャーを使って request.serverName を取得し、request.serverName を変数に格納してから、レスポンス・ハッシュマップで entryInstanceList および lastUpdated と併せて返すようにしても構いません。

完全に動的にするには、request.scheme を使用して http を返し、request.serverPort を使用して 80 を返すことができます (request.serverName 変数を使わないようにしなければならないのは、id のなかだけです。これについては、追って説明します)。

Atom フィードは一般的に、複数の異なるフォーマットでリンクを提供します。type 属性を見るとわかるように、このフィードの場合には 2 つのフォーマットがあります。1 つは HTML リンク、そしてもう 1 つはフィード自体に戻る Atom フォーマットです。自己リンクは極めて役に立ちます。例えば、Atom 文書を自分でダウンロードしていなかったとすると、自己リンクが標準ソースに戻るパンくずリストの手掛かりになるからです。

id フィールドは Atom フィードに固有の識別子で、URI とも異なり、フィードをダウンロード可能な現行のロケーションとも異なります (上記で説明したとおり、フィードの現行ソースを指定するのは <link> 要素です)。このサンプルでは固有の永続的 ID ストリングを生成するために、Mark Pilgrim によって概説されている手法を使用しています。この手法は、ドメイン名、フィードが利用可能になった最初の日付、そして URI の残りの部分を組み合わせるというものです (詳細は、「参考文献」を参照してください)。

id の各部分は id 全体としての一意性に比べれば、ほとんど重要ではありません。うかつにもこの id にコントローラーからの変数を指定することで、いつの間にか id の値が変わってしまうようなことがないよう注意してください。フィードの id は一意かつ不変であることが必須です。サーバーのアドレスが変更されたとしても、コンテンツが変更されない限り、フィードの id は同じでなければなりません。

updated フィールドは特定のフォーマット (2003-12-13T18:30:02Z のようなフォーマット、正確には RFC 3339 に記載されています) にする必要があります (詳細は、「参考文献」を参照してください)。そのため、atomDate クロージャーを既存の grails-app/taglib/DateTagLib.groovy ファイルに追加します (リスト 18 を参照)。


リスト 18. atomDate タグを追加する

import java.text.SimpleDateFormat

class DateTagLib {
  public static final String INCOMING_DATE_FORMAT = "yyyy-MM-dd hh:mm:ss"
  public static final String ATOM_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'-07:00'"

  def atomDate = {attrs, body ->
    def b = attrs.body ?: body()
    def d = new SimpleDateFormat(INCOMING_DATE_FORMAT).parse(b)
    out << new SimpleDateFormat(ATOM_DATE_FORMAT).format(d)
  }

  //SNIP
}

Atom フィードを完成させるため、grails-app/views/entry/_atomEntry.gsp を作成し、リスト 19 のコードを追加してください。


リスト 19. _atomEntry.gsp 部分テンプレート

<entry xmlns='http://www.w3.org/2005/Atom'>
  <author>
    <name>${entryInstance.author.name}</name>
  </author>
  <published><g:atomDate>${entryInstance.dateCreated}</g:atomDate></published>
  <updated><g:atomDate>${entryInstance.lastUpdated}</g:atomDate></updated>
  <link href="http://blogito.org/blog/${entryInstance.author.login}/
    ${entryInstance.title.encodeAsUnderscore()}" rel="alternate" 
    title="${entryInstance.title}" type="text/html" />
  <id>tag:blogito.org,2009:/blog/${entryInstance.author.login}/
    ${entryInstance.title.encodeAsUnderscore()}</id>
  <title type="text">${entryInstance.title}</title>
  <content type="xhtml">
    <div xmlns="http://www.w3.org/1999/xhtml">
      ${entryInstance.summary}
    </div>
  </content>
</entry>

最後に必要な作業は、Atom フィードを認証されていないユーザーに公開することです。EntryController.groovy で beforeInterceptor をリスト 20 のように調整してください。


リスト 20. Atom フィードを非認証ユーザーに公開する

class EntryController {

  def beforeInterceptor = [action:this.&auth, except:["index", "list", "show", "atom"]]

  //SNIP
}

Grails を再起動して http://localhost:9090/blogito/entry/atom にアクセスすると、整形式 Atom フィードが表示されるはずです (リスト 21 を参照)。


リスト 21. 整形式 Atom フィード

<feed xmlns="http://www.w3.org/2005/Atom">
  <title type="text">News from Blogito.org</title>
  <link rel="alternate" type="text/html" href="http://blogito.org/"/>
  <link rel="self" type="application/atom+xml" href="http://blogito.org/entry/atom" />
  <updated>2009-04-20T00:03:34-07:00</updated>
  <author><name>Blogito.org</name></author>
  <id>tag:blogito.org,2009-01-01:entry/atom</id>
  <generator uri="http://blogito.org" version="0.1">Hand-rolled Grails code</generator>

<entry xmlns='http://www.w3.org/2005/Atom'>
  <author>
    <name>Jane Smith</name>
  </author>
  <published>2009-04-20T00:03:34-07:00</published>
  <updated>2009-04-20T00:03:34-07:00</updated>
  <link href="http://blogito.org/blog/jsmith/Testing_with_Groovy" rel="alternate" 
    title="Testing with Groovy" type="text/html" />
  <id>tag:blogito.org,2009:/blog/jsmith/Testing_with_Groovy</id>
  <title type="text">Testing with Groovy</title>
  <content type="xhtml">
    <div xmlns="http://www.w3.org/1999/xhtml">
      See Practically Groovy
    </div>
  </content>

<!-- SNIP -->

</entry>
</feed>

Atom のセマンティクスにまるで馴染みがないとしても、Grails で Atom フィードを生成するメカニズムは至ってわかりやすいはずです。




上に戻る


Atom フィードの妥当性検証

フィードが正真正銘の整形式 Atom であることを確認するには、W3C のオンライン Feed Validator にアクセスしてください (「参考文献」を参照)。フィードが一般にアクセスできる URI に配置されていれば、ホーム・ページにその URI を貼り付けて Check をクリックすればよいのですが、この例での Atom フィードはローカル・ホストで実行されているため、Validate by Direct Input をクリックし、フィードからの出力を貼り付けます。図 4 にその結果を示します。


図 4. W3C バリデーター

指定された URI で自己リンクが使用可能でない (当たり前のことです) という警告を除けば、この Atom フィードは妥当性があり、本番で使用可能であると見なされます。




上に戻る


フィード・アイコンの追加

最後の仕上げとして、フィードへのリンクをヘッダーに追加します。至るところで見掛けるフィードのアイコンは、Web 上のさまざまな場所からダウンロードすることができます。このアイコンはオープンソースの Mozilla ライセンスの下でリリースされています (「参考文献」を参照)。

ダウンロードしたファイルを web-app/images にコピーしてから、grails-app/views/layouts/_header.gsp をリスト 22 のように微調整します。


リスト 22. フィード・アイコンをヘッダーに追加する

<div id="header">
  <p><g:link class="header-main" controller="entry">Blogito</g:link></p>
  <p class="header-sub">
    <g:link controller="entry" action="atom">
    <img src="${createLinkTo(
        dir:'images',file:'feed-icon-28x28.png')}" alt="Subscribe" title="Subscribe"/>
    </g:link>
    A tiny little blog
  </p>

  <div id="loginHeader">
    <g:loginControl />
  </div>
</div>

その結果、図 5 のようなホーム・ページが完成します。


図 5. フィード・アイコンが表示された Blogito ホーム・ページ




上に戻る


まとめ

今回の記事では、ファイルのアップロード機能、そして Atom 配信フィードを追加しました。これで Blogito は完成し、この「ごく簡易的な」ブログ・サーバーは稼働するようになりました。どれほど簡易的かと言えば、2 つのドメイン・クラス、2 つのコントローラー、そしてわずか 250 行余りのコードしかありません。これを確かめるには、grails stats と入力してみてください。その結果はリスト 23 のとおりです。


リスト 23. Blogito のサイズ

$ grails stats

	+----------------------+-------+-------+
	| Name                 | Files |  LOC  |
	+----------------------+-------+-------+
	| Controllers          |     2 |   127 |
	| Domain Classes       |     2 |    34 |
	| Tag Libraries        |     3 |    66 |
	| Unit Tests           |     6 |    24 |
	| Integration Tests    |     1 |    10 |
	+----------------------+-------+-------+
	| Totals               |    14 |   261 |
	+----------------------+-------+-------+

このアプリケーションは 4 回の記事にわたって作成しましたが、Grails について実用的な知識をしっかり身につければ、すべての開発作業は 1 日もあれば完了するはずです。

Blogito を組み立てる作業を楽しんでいただけたでしょうか。次回はコメント、タグなどのサポートをそれぞれに対応するプラグインによって追加します。今後の記事では、皆さんと一緒に Grails プラグイン・エコシステムについて掘り下げていきたいと思います。それでは、次回の記事まで Grails を楽しみながらマスターしてください。



参考文献

学ぶために
  • 連載「Grails をマスターする」: この連載の他の記事を読んで、Grails とこのフレームワークを使って実現可能なすべての内容をよく理解してください。

  • Grails: Grails の Web サイトにアクセスしてください。

  • Grails Framework Reference Documentation: この Grails のバイブルに、<select> タグと <uploadForm> タグに関する資料が載っています。

  • The Atom Syndication Format: IETF RFC 4287 が Atom の仕様です。

  • Atom: このウィキペディアの記事に Atom フィードのサンプルが記載されています・

  • Atom Syndication Format: developerWorks リソースで、Atom についての詳細を調べてください。

  • How to make a good ID in Atom」(Mark Pilgrim 著、dive into mark、2004年5月): Pilgrim による固有の永続的 Atom ID を生成する手法について詳しく学んでください。

  • Date and Time on the Internet: Timestamps: この記事の Atom id 生成手法で使用した日時フォーマットは、RFC 3339 によって定義されています。

  • W3C Feed Validation Service: このサービスを使用して、Atom フィードが整形式であることを確認してください。

  • Groovy Recipes』(Scott Davis 著、Pragmatic Programmers、2008年): Scott Davis の最新の著作で Groovy と Grails の詳細を学んでください。

  • 連載「実用的な Groovy」: developerWorks のこの連載記事では、実用的な Groovy の使用方法を探り、それぞれの方法をいつ、どんな場合に適用するかを解説しています。

  • Groovy: Groovy の Web サイトで、このプロジェクトの詳細を学んでください。

  • AboutGroovy.com: Groovy に関する最新のニュースと記事へのリンクが記載されています。

  • Technology bookstore: この記事で紹介した技術やその他の技術に関する本を参照してください。

  • developerWorks Java technology ゾーン: Java プログラミングのあらゆる側面を網羅した記事が豊富に用意されています。


製品や技術を入手するために
  • Grails: Grails の最新リリースをダウンロードしてください。

  • Feeds Plugin: RSS/Atom フィード、そして iTunes 対応のポッドキャストをレンダリングする Grails プラグインです。

  • フィード・アイコン: Feed Icon サイト、または Wikimedia Commons から標準フィード・アイコンを入手することができます。

  • Blogito: Blogito アプリケーションの完成版をダウンロードできます。


議論するために


著者について

Scott Davis

Scott Davis は国際的に知られた著者、講演者、そしてソフトウェア開発者で、Groovy と Grails の教育を目的とした会社、ThirstyHead.com の創設者でもあります。彼の著書には、『Groovy Recipes: Greasing the Wheels of Java』、『GIS for Web Developers: Adding Where to Your Application』、『The Google Maps API』、『JBoss At Work』などがあります。現在、IBM developerWorks の「Grails をマスターする」と「実用的な Groovy」の 2 本の連載を執筆中です。




記事の評価


サイト改善のため、ご意見をお寄せください。こちらのフォームからお願いいたします。



 


 


不充分・不完全である大変素晴らしい
 


この記事を共有する

del.icio.us del.icio.us newsing newsing FC2ブックマーク FC2ブックマーク
Choix! Choix! ニフティクリップ ニフティクリップ Yahoo!ブックマーク Yahoo!ブックマーク
MM/memo MM/memo CZブックマーク CZブックマーク livedoorクリップ livedoorクリップ
はてなブックマーク はてなブックマーク Buzzurl(バザール) Buzzurl(バザール)




上に戻る


    日本IBMについて プライバシー お問い合わせ