Wicketで始めるオブジェクト指向ウェブ開発

第5回Twitterタイムラインで見るWicketのオブジェクト指向プログラミング(前編)

前回までで、Twitterにログインしタイムライン表示用ページに遷移するところまでは実現できました。今回は、いよいよTwitterタイムラインを表示します。

Twitterタイムラインは、Twitterユーザの発言をリスト上に表示します。HTML上で見ると、同じ構造のタグが発言数分、ずらりと並んだ形をしています。JSPでそのような構造を作るときには、Javaのfor文もしくは同じような動作をするカスタムタグを使って、タグを繰り返し表示することで実現しました。

Wicketでは、このような繰り返しリスト全体を「繰り返し項目コンポーネント」として扱います。それがListViewクラスです。

ListViewによるコンポーネントの繰り返し

ListViewはHTML要素を繰り返すときに使うコンポーネントです。ListViewに対応するwicket:idは、⁠繰り返したいHTML要素」につけます。たとえば、次のようにすれば、repeatViewというwicket:idのついた<tr>タグ(内側の<td>タグも含めた全体)を繰り返し生成することができます。

リスト1 twitterスクリーンネーム一覧を表示するためのHTML
<table>
  <tr wicket:id="repeatView">
    <td wicket:id="userName">twitterのスクリーンネーム</td>
  </tr>
</table>

この<tr>要素を繰り返すためのプログラムが、次のプログラムです。

リスト2 ListViewの使い方
ListView view = new ListView("id", new TwitterStatusListModel<List<Status>>()) {
  @Override
  protected void populateItem(final ListItem<Status> item) {
    //itemが行を表すコンポーネント
    //itemにコンポーネントをadd()することで、1行のコンポーネントを組み立てる
    Status status = item.getModelObject();
    item.add(new Label("userName", status.getUser().getScreenName()));
  }
}

ListViewがデータを表示する際には、自身のモデルのgetObject()を呼び出します。モデルはgetObject()呼び出しの結果としてListを返します。ListViewはListの要素ひとつひとつを使ってpopulateItem()メソッドを呼び出します。populateItem()メソッドの引数は常にListItemで、このListItemが表の1行を表します。上記例でいえば、ListItemは<tr>タグを表しています。まさに、wicket:idをつけたタグです。wicket:idをつけたタグが、ListItemとして繰り返しpopulateItem()メソッドに渡されるのです。

ListViewに渡しているモデル「TwitterStatusListModel」はList<Status>型オブジェクトを返します。ListViewはこのListからStatusオブジェクトを1つずつ、populateItem()メソッドに渡します。Statusは行を表すコンポーネントであるListItemのモデルから取り出すことができます。item.getModelObject()が、ListItemからStatusを取り出しています。

あとはStatusオブジェクトを使って1行分のコンポーネントを組み立てるだけです。ListItemにコンポーネントを追加することで、<tr>タグの子要素を組み立てることができます。

この例では、wicket:id="userName"に対応するLabelコンポーネントを適用しています。LabelコンポーネントはStatusオブジェクトから取得できるスクリーンネームを表示します。

LoadableDetachableModelでリクエスト中のキャッシュを行う

ListViewの作り方がおおよそ分かりました。しかし、TwitterStatusListModelのような、Statusのリストを返すモデルがなければこのプログラムは動きません。

サンプルコード内のjp.gihyo.wicket.page.simple.Timelineクラスでは、TwitterStatusListModelの代わりに次のようなプログラムでTwitterのステータスを取得しています。

リスト3 Twitterのステータスリストを返すモデル
IModel<List<Status>> statusModel = new LoadableDetachableModel<List<Status>>() {
  @Override
  protected List<Status> load() {
    try {
      Twitter twitter = AppSession.get().getTwitterSession();
      return twitter.getFriendsTimeline(new Paging(1, ITEMS_PER_PAGE));
    } catch (TwitterException ex) {
      MyTimeline.this.error(getString("canNotRetrieveFriendTimeline"));
      return Collections.emptyList();
    }
  }
};

Statusクラスは、twitter4jが提供するTwitterのステータスを表現するクラスです。TwitterタイムラインはListView<Status>によって表示しますので、必要なモデル型はIModel<List<Status>>です。ListViewは常に自分が使うオブジェクトのListをモデルに期待します。IModel<List<Status>>に適合するモデルであれば何でもかまいません。

ここでは、見慣れないクラスLoadableDetachableModelを使いました。

LoadbaleDetachableModelクラスは、リクエスト毎の初回アクセス時にデータをロードし、キャッシュするモデルです。一度キャッシュされたデータは、レスポンスが返るまでは使い回されます。Twitterのステータス取得はTwitterサイトへのリモートAPI呼び出しを伴いますので、非常に重い処理です。リクエスト中に何度も呼ばれるとアプリケーション全体が重くなります。LoadableDetachableModelはこのようなケースに便利なモデル実装です。

Statusの取得

LoadbaleDetachableModelのload()メソッドをオーバーライドすることで、データのロード処理を記述します。

サンプルでは、前回までに作ったAppSessionクラスのgetTwitterSession()メソッドを使って、ログイン時に生成したTwitterオブジェクトを取得し、getFriendsTimeline()メソッドでタイムラインを取得します。結果値はList<Status>ですので、モデルの型と一致します。

getFriendsTimeline()は失敗する可能性がある操作です。失敗した場合にはTwitterExceptionがスローされますので、キャッチしてエラーメッセージを表示します。ここでは、FeedbackPanelに情報を表示するerror()メソッドを呼び出しています[1]⁠。

また、エラーの時でもモデルは何らかの値を返す必要がありますので、空のリストを返却しています。

このモデルをListViewに渡すことで、タイムライン表示リストを作ることができます。

タイムラインの表示

モデルができてしまえば、タイムラインはListViewのpopulateItem()メソッドを実装することで簡単に作成できます。

サンプルコードでは、次のようにListItemに次々とコンポーネントを追加することで、1行分のコンポーネントを構築しています。

リスト4 Twitterタイムラインの構築
final Status status = item.getModelObject();
String userUrl = "http://twitter.com/" + status.getUser().getScreenName();
ExternalLink imageLink = new ExternalLink("imageLink", userUrl);
Image userImage = new Image("userImage", new WebResource() {
    @Override
    public IResourceStream getResourceStream() {
        return new UrlResourceStream(status.getUser().getProfileImageURL());
    }
});
imageLink.add(userImage);
item.add(imageLink);

ExternalLink screenNameLink = new ExternalLink("screenName", userUrl, status.getUser().getScreenName());
item.add(screenNameLink);
(以下省略)

見慣れないクラスはあるでしょうが、プログラム例「ListViewの使い方」で見たのと同じように、ListItemコンポーネントにLabelなどのコンポーネントを追加していっているだけなことがわかるでしょう。

ここではじめて出てきたコンポーネントが2つあります。1つはExternalLinkクラスで、その名の通りアプリケーション外部へのリンクを動的に生成するためのコンポーネントです。最も単純な使い方は、この例のように、第2引数としてリンク先URL文字列を渡すことです。第3引数はページ上に表示するリンク文字列です。

もう1つはImageコンポーネントです。画像を動的に生成して表示するコンポーネントです。HTMLの<img>タグに適用します。

Imageコンポーネントの第2引数は、WebResourceです。これは、Wicketがページ以外のリソースを扱うために扱うためのクラスです。

WebResourceとResourceStream

WebResourceは画像や文字列、ファイルといった情報を扱うためのクラスです。Wicketではそれらを「Resource」というクラスで扱います。WebResourceクラスは、Wicketが標準で提供している、Resourceの唯一のサブクラスです。

Resourceの一番大きな役割は、ResourceStreamを作り出すことです。ResourceStreamは、サーバ側にあるファイルなどのリソースとブラウザの間にある「データの流れ(ストリーム⁠⁠」を表してます。ブラウザはストリームを読み込むことで、サーバからリソースを取得するのです。

サンプルでは、WebResourceのgetResourceStream()メソッドをオーバーライドすることでResourceStreamを作り出しています。ResourceStreamにはFileResourceStream, ZipResourceStreamなど、標準で多くのクラスが提供されていますが[2]⁠、ここでは特定URLからデータを取得してストリーム化するためのUrlResourceStreamを使用しています。

今回のサンプルにおけるImageコンポーネントの役割は、ユーザのアイコン画像を表示することです。画像URLはstatus.getUser().getProfileImageURL()で取り出すことができますので、このURLをUrlResourceStreamに渡すだけで、簡単にResourceStreamを生成できます。

Imageコンポーネントを<img>タグに適用すると、WebResourceへのURLを動的に生成し、<img>タグのsrc属性に挿入します。ブラウザはsrc属性を見てWicketにアクセスしてきますので、WicketはWebResourceからResourceStreamを作りだし、ブラウザにデータを返却するのです。

WebResourceを使えば、画像はもちろんのこと、ファイルやバイナリデータなど、多彩なデータをWicketで扱うことができます。ResourceStreamにも多彩なクラスが用意されており、クラスを差し替えるだけで多くのことを実現できます。Wicketは、ストリームもオブジェクトとして扱い、再利用可能としているのです[3]⁠。

次回:リンク

ウェブページにおいて必ず使うものといえば、やはりリンクでしょう。リンクがなければウェブは成り立ちません。

WicketではリンクはLinkというコンポーネントによって扱うことができます。Linkコンポーネントを使うことで、ページ上のあらゆるタグをクリック可能にできます。

次回は、Linkコンポーネントを使って各行にリンクを生成します。

おすすめ記事

記事・ニュース一覧