Symfoware

Symfowareについての考察blog

Google Web Toolkit 2.1 はてなブックマークエントリーをJSONPで取得 その3

Google Web Toolkit 2.1のチュートリアルを試してみています。
Google Web Toolkit に関する記事の一覧


JSONPでデータが取得できるところまで、前回試してみました。
Google Web Toolkit 2.1 はてなブックマークエントリーをJSONPで取得 その2

今回は、JSONPで取得したデータを全てオブジェクトに格納してみます。


Making cross-site requests
こちらを参考に、プログラムを作成しました。




配列データ(bookmarks)の取得



前回は、一番変換が簡単そうなデータだけ取得してみました。


count(ブックマークしている合計ユーザ数)
url(ブックマークされているURL)
title(タイトル)
eid(エントリーID)
entry_url(はてなブックマークエントリーページのURL)
screenshot(スクリーンショット画像のURL)





今回は、配列で定義されている「bookmarks」のデータを取得してみます。
bookmarks配列の個々の内容はこんな感じになります。


comment(コメント)
timestamp(ブックマークした時刻)
user(ブックマークしたユーザ名)
tags(タグの配列)





かなり手探りでしたが、まずbookmarks配列用のオブジェクトを
定義しました。

パッケージ・エクスプローラーで「com.fc2.blog68.symfoware.hatena.client」を
選択した状態でEclipseの[ファイル]-[新規]-[クラス]を選択。

名前に「HatenaBookmarkData」
スーパークラスに「com.google.gwt.core.client.JavaScriptObject」
と入力して、完了を押下します。

14_001.png



クラスの内容は以下のとおり。


package com.fc2.blog68.symfoware.hatena.client;

import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArrayString;

public class HatenaBookmarkData extends JavaScriptObject {
    
    protected HatenaBookmarkData() {
    }
    
    // コメント
    public final native String getComment() /*-{ return this.comment; }-*/;
    
    // ブックマークした時刻
    public final native String getTimestamp() /*-{ return this.timestamp; }-*/;
    
    // ブックマークしたユーザ名
    public final native String getUser() /*-{ return this.user; }-*/;
    
    // タグの配列
    public final native JsArrayString getTags() /*-{ return this.tags; }-*/;
}



タグの配列は、文字列の配列となるのですが、この戻り値に
何を指定すればよいかわからずはまりました。

最初、戻り値をString[]としていたのですが、これだと
オブジェクト変換時にエラーになります。

どうやら、「com.google.gwt.core.client.JsArrayString」を
使えばJSONの文字列配列を表現できるようです。


前回作成した「HatenaData」クラスにブックマークの配列を
取得するためのメソッド「getBookmarks()」を追加します。




package com.fc2.blog68.symfoware.hatena.client;

import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;

public class HatenaData extends JavaScriptObject {
    
    protected HatenaData() {
    }
    
    // ブックマークしている合計ユーザ数
    public final native String getCount() /*-{ return this.count; }-*/;
    
    // ブックマークされているURL
    public final native String getUrl() /*-{ return this.url; }-*/;
    
    // タイトル
    public final native String getTitle() /*-{ return this.title; }-*/;
    
    // エントリーID
    public final native String getEid() /*-{ return this.eid; }-*/;
    
    // はてなブックマークエントリーページのURL
    public final native String getEntryUrl() /*-{ return this.entry_url; }-*/;
    
    // スクリーンショット画像のURL
    public final native String getScreenShot() /*-{ return this.screenshot; }-*/;
    
    // bookmarks配列を取得
    public final native JsArray<HatenaBookmarkData> getBookmarks() /*-{ return this.bookmarks; }-*/;
}






getBookmarks()の戻り値を以下のように定義するのがミソ。


JsArray<HatenaBookmarkData>








テーブルにデータを表示する処理



これでデータの取得と変換が行えるようになりました。
テーブルに取得したデータを表示する処理を追加します。


package com.fc2.blog68.symfoware.hatena.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.http.client.URL;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextBox;


public class Hatena implements EntryPoint {
    
    // 検索するURLを入力するテキストボックス
    private TextBox findText = new TextBox();
    // 検索事項ボタン
    private Button findButton = new Button("検索");
    
    // 検索条件、検索ボタンを配置するパネル
    private HorizontalPanel findPanel = new HorizontalPanel();
    // 検索結果を表示するためのテーブル
    private FlexTable bookmarksFlexTable = new FlexTable();
    
    // はてなAPIのURL
    private static final String JSON_URL = "http://b.hatena.ne.jp/entry/json/?url=";
    
    // デバッグ用のラベル
    private Label debugLabel = new Label();
    
    
    /**
     * HTML描画時に呼び出されるメソッド
     */
    public void onModuleLoad() {
        
        // 検索テキスト、ボタンを水平パネルに配置
        findText.setWidth("300px");
        findPanel.add(findText);
        findPanel.add(findButton);
        
        // 検索するURLを入力するテキストボックスの初期値は、blogのURL
        findText.setText("http://symfoware.blog68.fc2.com/");
        
        // テーブルの作成
        bookmarksFlexTable.setText(0, 0, "ユーザー");
        bookmarksFlexTable.setText(0, 1, "コメント");
        bookmarksFlexTable.setText(0, 2, "ブックマーク時刻");
        bookmarksFlexTable.setText(0, 3, "タグ");
        
        // HTMLに記載したdiv name="rootPanel"の位置に部品を配置
        RootPanel.get("rootPanel").add(findPanel);
        RootPanel.get("rootPanel").add(bookmarksFlexTable);
        RootPanel.get("rootPanel").add(debugLabel);
        

        // 初期フォーカスは、検索URL入力テキストに設定
        findText.setFocus(true);
        
        // クリック時、データの検索を行う
        findButton.addClickHandler(new ClickHandler() {
            public void onClick(ClickEvent event) {
                findData();
            }
        });
    }
    
    private void findData(){
        
        debugLabel.setText("");
        
        String url = JSON_URL;
        
        // はてなAPI用のURLに、テキストボックスに入力された文字列を連結
        url += findText.getText();
        
        // callback関数名を連結
        url = URL.encode(url) + "&callback=mycall";
        
        getJson(url, this);
    }
    
    public native static void getJson(String url, Hatena handler) /*-{
        var callback = "mycall";
        
        // [1] Create a script element.
        var script = document.createElement("script");
        script.setAttribute("src", url);
        script.setAttribute("type", "text/javascript");
        
        // [2] Define the callback function on the window object.
        window[callback] = function(jsonObj) {
            // [3]
            handler.@com.fc2.blog68.symfoware.hatena.client.Hatena::handleJsonResponse(Lcom/google/gwt/core/client/JavaScriptObject;)(jsonObj);
            window[callback + "done"] = true;
        }
        
        // [4] JSON download has 1-second timeout.
        setTimeout(function() {
            if (!window[callback + "done"]) {
                handler.@com.fc2.blog68.symfoware.hatena.client.Hatena::handleJsonResponse(Lcom/google/gwt/core/client/JavaScriptObject;)(null);
            }
            
            // [5] Cleanup. Remove script and callback elements.
            document.body.removeChild(script);
            delete window[callback];
            delete window[callback + "done"];
        }, 1000);

        // [6] Attach the script element to the document body.
        document.body.appendChild(script);
    }-*/;
    
    
    public void handleJsonResponse(JavaScriptObject jso) {
        if (jso == null) {
            debugLabel.setText("Couldn't retrieve JSON");
            return;
        }

        HatenaData data = asHatenaData(jso);
        
        for (int i = 0; i < data.getBookmarks().length(); i++) {
            HatenaBookmarkData bookmark = data.getBookmarks().get(i);
            
            bookmarksFlexTable.setText(i + 1, 0, bookmark.getUser());
            bookmarksFlexTable.setText(i + 1, 1, bookmark.getComment());
            bookmarksFlexTable.setText(i + 1, 2, bookmark.getTimestamp());
            bookmarksFlexTable.setText(i + 1, 3, bookmark.getTags().join(":"));
        }
    }
    
    private final native HatenaData asHatenaData(JavaScriptObject jso) /*-{
        return jso;
    }-*/;
}






開発用のサーバーを起動して実行してみると

14_002.png


非常に画面が地味ですが、ちゃんと情報を取得できていることが
確認できました。








関連記事



Google Web Toolkit 2.1 はてなブックマークエントリーをJSONPで取得 その2
Google Web Toolkit に関する記事の一覧






テーマ:プログラミング - ジャンル:コンピュータ

  1. 2010/11/11(木) 22:42:36|
  2. Google Web Toolkit
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集

Google Web Toolkit 2.1 はてなブックマークエントリーをJSONPで取得 その2

Google Web Toolkit 2.1のチュートリアルを試してみています。
Google Web Toolkit に関する記事の一覧


雛形で生成されたプログラムを変更するところまで、前回試してみました。
Google Web Toolkit 2.1 はてなブックマークエントリーをJSONPで取得 その1

今回は、JSONPでデータを取得するロジックを考えたいと思います。


Making cross-site requests
こちらを参考に、プログラムを作成しました。




JavaScriptObjectを継承したクラスの作成



取得したデータを解析し、オブジェクトに変換するための
JavaScriptObjectを継承したクラスを作成します。

パッケージ・エクスプローラーで「com.fc2.blog68.symfoware.hatena.client」を
選択状態にしておき、[ファイル]-[新規作成]-[クラス]を選択。

クラス名を「HatenaData」
スーパークラスに「com.google.gwt.core.client.JavaScriptObject」
を入力して、完了を押下。

13_001.png



まず、一番上位にある変換が簡単そうなデータから。
以下の情報をJSON形式のデータからマッピングするクラスを作成してみます。


count(ブックマークしている合計ユーザ数)
url(ブックマークされているURL)
title(タイトル)
eid(エントリーID)
entry_url(はてなブックマークエントリーページのURL)
screenshot(スクリーンショット画像のURL)





「HatenaData.java」の内容はこんな感じになりました。


package com.fc2.blog68.symfoware.hatena.client;

import com.google.gwt.core.client.JavaScriptObject;

public class HatenaData extends JavaScriptObject {
    
    protected HatenaData() {
    }
    
    // ブックマークしている合計ユーザ数
    public final native String getCount() /*-{ return this.count; }-*/;
    
    // ブックマークされているURL
    public final native String getUrl() /*-{ return this.url; }-*/;
    
    // タイトル
    public final native String getTitle() /*-{ return this.title; }-*/;
    
    // エントリーID
    public final native String getEid() /*-{ return this.eid; }-*/;
    
    // はてなブックマークエントリーページのURL
    public final native String getEntryUrl() /*-{ return this.entry_url; }-*/;
    
    // スクリーンショット画像のURL
    public final native String getScreenShot() /*-{ return this.screenshot; }-*/;
    
    // TODO
    // bookmarksを配列で宣言
}








データ取得と変換部分の作成



入力されたURLをパラメーターに付加し、JSON形式のデータを取得できるようにします。
あまり内容の詳細を理解できていませんが・・・参考サイトに
記載されているソースから試行錯誤して作成したソースがこちら。




package com.fc2.blog68.symfoware.hatena.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.http.client.URL;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextBox;


public class Hatena implements EntryPoint {
    
    // 検索するURLを入力するテキストボックス
    private TextBox findText = new TextBox();
    // 検索事項ボタン
    private Button findButton = new Button("検索");
    
    // 検索条件、検索ボタンを配置するパネル
    private HorizontalPanel findPanel = new HorizontalPanel();
    // 検索結果を表示するためのテーブル
    private FlexTable stocksFlexTable = new FlexTable();
    
    // はてなAPIのURL
    private static final String JSON_URL = "http://b.hatena.ne.jp/entry/json/?url=";
    
    // デバッグ用のラベル
    private Label debugLabel = new Label();
    
    
    /**
     * HTML描画時に呼び出されるメソッド
     */
    public void onModuleLoad() {
        
        // 検索テキスト、ボタンを水平パネルに配置
        findText.setWidth("300px");
        findPanel.add(findText);
        findPanel.add(findButton);
        
        // 検索するURLを入力するテキストボックスの初期値は、blogのURL
        findText.setText("http://symfoware.blog68.fc2.com/");

        // HTMLに記載したdiv name="rootPanel"の位置に部品を配置
        RootPanel.get("rootPanel").add(findPanel);
        RootPanel.get("rootPanel").add(stocksFlexTable);
        RootPanel.get("rootPanel").add(debugLabel);
        

        // 初期フォーカスは、検索URL入力テキストに設定
        findText.setFocus(true);
        
        // クリック時、データの検索を行う
        findButton.addClickHandler(new ClickHandler() {
            public void onClick(ClickEvent event) {
                findData();
            }
        });
    }
    
    private void findData(){
        
        debugLabel.setText("");
        
        String url = JSON_URL;
        
        // あてなAPI用のURLに、テキストボックスに入力された文字列を連結
        url += findText.getText();
        
        // callback関数名を連結
        url = URL.encode(url) + "&callback=mycall";
        
        getJson(url, this);
    }
    
    public native static void getJson(String url, Hatena handler) /*-{
        var callback = "mycall";
        
        // [1] Create a script element.
        var script = document.createElement("script");
        script.setAttribute("src", url);
        script.setAttribute("type", "text/javascript");
        
        // [2] Define the callback function on the window object.
        window[callback] = function(jsonObj) {
            // [3]
            handler.@com.fc2.blog68.symfoware.hatena.client.Hatena::handleJsonResponse(Lcom/google/gwt/core/client/JavaScriptObject;)(jsonObj);
            window[callback + "done"] = true;
        }
        
        // [4] JSON download has 1-second timeout.
        setTimeout(function() {
            if (!window[callback + "done"]) {
                handler.@com.fc2.blog68.symfoware.hatena.client.Hatena::handleJsonResponse(Lcom/google/gwt/core/client/JavaScriptObject;)(null);
            }
            
            // [5] Cleanup. Remove script and callback elements.
            document.body.removeChild(script);
            delete window[callback];
            delete window[callback + "done"];
        }, 1000);

        // [6] Attach the script element to the document body.
        document.body.appendChild(script);
    }-*/;
    
    
    public void handleJsonResponse(JavaScriptObject jso) {
        if (jso == null) {
            debugLabel.setText("Couldn't retrieve JSON");
            return;
        }

        HatenaData data = asHatenaData(jso);
        debugLabel.setText(data.getEntryUrl() + ":" + data.getTitle() + ":" + data.getCount());
    }
    
    private final native HatenaData asHatenaData(JavaScriptObject jso) /*-{
        return jso;
    }-*/;
}






ちゃんとデータの取得と変換が行われれば、デバッグ用に用意したラベルに
・はてなブックマークエントリーページのURL
・タイトル
・ブックマークされている件数
が表示されるはずです。




実行結果



ここまでで、開発用のサーバーを起動し、動作を確認してみました。
検索ボタンを押すと、狙い通りの情報が表示されました。

13_002.png


次は、配列として格納されているbookmarksを変換する方法について
考えてみることにします。





関連記事



Google Web Toolkit 2.1 はてなブックマークエントリーをJSONPで取得 その3
Google Web Toolkit に関する記事の一覧





テーマ:プログラミング - ジャンル:コンピュータ

  1. 2010/11/10(水) 23:49:08|
  2. Google Web Toolkit
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集

Google Web Toolkit 2.1 はてなブックマークエントリーをJSONPで取得 その1

Google Web Toolkit 2.1のチュートリアルを試してみています。
Google Web Toolkit に関する記事の一覧


前回はJSONを使用したクライアントとサーバー間の通信を試しました。
Google Web Toolkit 2.1 JSON形式のデータの利用

今回は、JSONPでのデータ取得を試してみます。


Making cross-site requests
こちらを参考に、プログラムを作成しました。




1. Reviewing the requirements and design



参考にしたサイトでは、JSONPで返却する値を検討しています。
今回は、はてなブックマークのエントリー情報を取得するJSONPが
公開されていますので、それにアクセスしデータを取得・表示する
サンプルを作成してみようと思います。

はてなブックマークエントリー情報取得API


こちらのサイトを参考にさせていただきました。
ブラウザからJSONで呼び出せるサービス一覧
JSON with Padding Test


オフィシャルのAPI解説よりもJSON with Padding Testでの実行結果を
見たほうが返却されるデータ形式の理解が早いかと思います。


12_001_20101109224817.png


上位に

count(ブックマークしている合計ユーザ数)
url(ブックマークされているURL)
bookmarks(ユーザがブックマークしたデータの配列)
title(タイトル)
eid(エントリーID)
entry_url(はてなブックマークエントリーページのURL)
screenshot(スクリーンショット画像のURL)


という情報が並びます。


bookmarks配列のないようはこんな感じになるようです。

12_002_20101109224817.png



comment(ブックマークコメント)
timestamp(ブックマークした時刻)
user(ブックマークしたユーザ名)
tags(タグの配列)




受け取り用のオブジェクトを定義するときの参考になりそうです。





2. Creating a data a source



参考にしたサイトでは、仕様を満たすサーバーサイドのソースコードに
ついて解説されていますが、ここでは、はてなAPIに送るリクエスト
について考えて見ます。

といっても簡単で


http://b.hatena.ne.jp/entry/json/?url=[取得したいサイトのURL]&callback=[コールバック関数]



という形式でGETリクエストを送信すればよいようです。






3. Requesting the data from the remote server



これまで、「StockWatcher」というプロジェクトを作成し、徐々に
機能追加を行う形で学習してきました。

復習の意味もこめて、新しくプロジェクトを作成し、プログラムを
作成することにしました。

EclipseにGoogle Web Toolkit 2.1のプラグインをインストールしておきます。
Google Web Toolkit 2.1のEclipseプラグインをインストールする


Eclipseツールバーのアイコンをクリック。

12_003_20101109224817.png


プロジェクト名を「hatena」
パッケージを「com.fc2.blog68.symfoware.hatena」
として、プロジェクトを新規作成します。

12_004_20101109224817.png


雛形のファイルが作成されますが、以下のクラスは不要なので
削除しました。


com.fc2.blog68.symfoware.hatena.client.GreetingService
com.fc2.blog68.symfoware.hatena.client.GreetingServiceAsync
com.fc2.blog68.symfoware.hatena.server.GreetingServiceImpl
com.fc2.blog68.symfoware.hatena.shared.FieldVerifier



パッケージ・エクスプローラーでの表示です。
選択状態にあるソースを削除しました。

12_005_20101109224816.png





Hatena.gwt.xmlの編集



HTTPリクエストを行ううえで後々必要になると思いますので、
Hatena.gwt.xmlに一行「com.google.gwt.http.HTTP」を使用する
設定を追記しておきます。

以下、パッケージ・エクスプローラー上からみた「Hatena.gwt.xml」の
場所と、Hatena.gwt.xmlの記載内容です。

12_006.png



<?xml version="1.0" encoding="UTF-8"?>
<module rename-to='hatena'>
  <!-- Inherit the core Web Toolkit stuff.                        -->
  <inherits name='com.google.gwt.user.User'/>

  <!-- Inherit the default GWT style sheet. You can change     -->
  <!-- the theme of your GWT application by uncommenting         -->
  <!-- any one of the following lines.                            -->
  <inherits name='com.google.gwt.user.theme.standard.Standard'/>
  <!-- <inherits name='com.google.gwt.user.theme.chrome.Chrome'/> -->
  <!-- <inherits name='com.google.gwt.user.theme.dark.Dark'/>     -->

  <!-- Other module inherits                                     -->
  <!--=== 以下の一行を追加 ===-->
  <inherits name="com.google.gwt.http.HTTP" />

  <!-- Specify the app entry point class.                         -->
  <entry-point class='com.fc2.blog68.symfoware.hatena.client.Hatena'/>

  <!-- Specify the paths for translatable code                    -->
  <source path='client'/>
  <source path='shared'/>

</module>







Hatena.htmlの編集



Hatena.htmlを雛形の状態から編集しました。
idが「rootPanel」という名前のdivを追加し、プログラム中から
ここへ部品を配置することにします。

パッケージ・エクスプローラー上での「Hatena.html」の場所と
記載内容は以下のとおり。

12_007.png



<!doctype html>
<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <link type="text/css" rel="stylesheet" href="Hatena.css">
    <title>はてなブックマークエントリー情報取得サンプル</title>
    <script type="text/javascript" language="javascript" src="hatena/hatena.nocache.js"></script>
  </head>
  <body>

    <h1>はてなブックマークエントリー情報取得</h1>
    <div id="rootPanel" align="center"></div>
    
    <!-- OPTIONAL: include this if you want history support -->
    <iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1' style="position:absolute;width:0;height:0;border:0"></iframe>
    
    <!-- RECOMMENDED if your web app will not function without JavaScript enabled -->
    <noscript>
     <div style="width: 22em; position: absolute; left: 50%; margin-left: -11em; color: red; background-color: white; border: 1px solid red; padding: 4px; font-family: sans-serif">
        Your web browser must have JavaScript enabled
        in order for this application to display correctly.
     </div>
    </noscript>
  </body>
</html>








web.xmlの編集



雛形として出力されたweb.xmlにはサーブレットに関する記載が
ありますが、先ほどざっくり消しましたので、このまま起動しようと
するとエラーになってしまいます。

web.xmlのサーブレットに関する指定を消しておきます。
パッケージ・エクスプローラー上での場所と内容は以下のとおり。

12_008.png




<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>

  <!-- ここから==================
  <servlet>
    <servlet-name>greetServlet</servlet-name>
    <servlet-class>com.fc2.blog68.symfoware.hatena.server.GreetingServiceImpl</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>greetServlet</servlet-name>
    <url-pattern>/hatena/greet</url-pattern>
  </servlet-mapping>
  ==================ここまで削除 -->

  <welcome-file-list>
    <welcome-file>Hatena.html</welcome-file>
  </welcome-file-list>

</web-app>









Hatena.javaの編集



ここまでで下準備は整いました。
いよいよメインのHatena.javaを編集します。

初めてチュートリアルに沿わない状態でプログラムを作成しているので、まずは

・検索するURLを記載するテキストボックス(findText)
・検索実行ボタン(findButton)
・結果を表示するテーブル(stocksFlexTable)



これらを配置するだけのプログラムを書いてみました。

パッケージ・エクスプローラー上での「Hatena.java」の位置と
記載内容は以下のとおり。

12_009.png



package com.fc2.blog68.symfoware.hatena.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextBox;


public class Hatena implements EntryPoint {
    
    // 検索するURLを入力するテキストボックス
    private TextBox findText = new TextBox();
    // 検索事項ボタン
    private Button findButton = new Button("検索");
    
    // 検索条件、検索ボタンを配置するパネル
    private HorizontalPanel findPanel = new HorizontalPanel();
    // 検索結果を表示するためのテーブル
    private FlexTable stocksFlexTable = new FlexTable();
    
    /**
     * HTML描画時に呼び出されるメソッド
     */
    public void onModuleLoad() {
        
        // 検索テキスト、ボタンを水平パネルに配置
        findText.setWidth("300px");
        findPanel.add(findText);
        findPanel.add(findButton);
        
        // 検索するURLを入力するテキストボックスの初期値は、blogのURL
        findText.setText("http://symfoware.blog68.fc2.com/");

        // HTMLに記載したdiv name="rootPanel"の位置に部品を配置
        RootPanel.get("rootPanel").add(findPanel);
        RootPanel.get("rootPanel").add(stocksFlexTable);

        // 初期フォーカスは、検索URL入力テキストに設定
        findText.setFocus(true);

    }
}






ここまでで、デバッグ用のサーバーを起動して動作を確認しました。
起動は、実行ボタンの右側にある下矢印を押すと、「hatena」という
項目が追加されていますので、これを選択します。

12_010.png



サーバー起動後、
http://127.0.0.1:8888/Hatena.html?gwt.codesvr=127.0.0.1:9997
にアクセスすると、こんな表示になりました。

12_011.png



ここまでは狙い通り動いてくれているようです。





関連記事



Google Web Toolkit 2.1 はてなブックマークエントリーをJSONPで取得 その2
Google Web Toolkit に関する記事の一覧








テーマ:プログラミング - ジャンル:コンピュータ

  1. 2010/11/09(火) 22:51:11|
  2. Google Web Toolkit
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集

Google Web Toolkit 2.1 JSON形式のデータの利用

Google Web Toolkit 2.1のチュートリアルを試してみています。
Google Web Toolkit に関する記事の一覧


前回はGWT RPCを使用したクライアントとサーバー間の通信を試しました。
Google Web Toolkit 2.1 GWT RPCの使用

今回は、GWT RPCではなく普通のJSON形式のデータを取り扱う方法を試して見ます。


Retrieving JSON Data
こちらを参考に、プログラムを作成しました。




1. Creating a source of JSON data



StockWatcher.javaのrefreshWatchListメソッドで、株価をランダムに生成しています。
Google Web Toolkit 2.1 GWT RPCの使用
この記事では、GWT RPCを使用してサーバー側で株価を生成するように
変更しました。

今回は、株価をJavaのサーブレットで作成し、JSON形式のレスポンスを作成。
クライアント側のJavaScriptで解析し表示するように変更します。


パッケージ・エクスプローラーの
「com.google.gwt.sample.stockwatcher.client」
を選択した状態で、Eclipseのツールバーの[ファイル]-[新規]-[クラス]を選択します。

パッケージ名を「com.google.gwt.sample.stockwatcher.server」に変更
クラス名を「JsonStockData」
として、完了ボタンを押下。
新しいクラスを作成します。

11_001_20101109002730.png



作成した「JsonStockData」を以下の内容に変更します。


package com.google.gwt.sample.stockwatcher.server;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Random;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class JsonStockData extends HttpServlet {

    private static final long serialVersionUID = 1L;
    private static final double MAX_PRICE = 100.0; // $100.00
    private static final double MAX_PRICE_CHANGE = 0.02; // +/- 2%

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {

        Random rnd = new Random();

        PrintWriter out = resp.getWriter();
        out.println('[');
        String[] stockSymbols = req.getParameter("q").split(" ");
        for (String stockSymbol : stockSymbols) {

            double price = rnd.nextDouble() * MAX_PRICE;
            double change = price * MAX_PRICE_CHANGE
                    * (rnd.nextDouble() * 2f - 1f);

            out.println(" {");
            out.print("    \"symbol\": \"");
            out.print(stockSymbol);
            out.println("\",");
            out.print("    \"price\": ");
            out.print(price);
            out.println(',');
            out.print("    \"change\": ");
            out.println(change);
            out.println(" },");
        }
        out.println(']');
        out.flush();
    }

}





このサーブレットにアクセスできるよう、WEB-INF/web.xmlを編集します。


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>

  <!-- Servlets -->
  <servlet>
    <!--====== jsonStockDataというサーブレットを定義 ======-->
    <servlet-name>jsonStockData</servlet-name>
    <servlet-class>com.google.gwt.sample.stockwatcher.server.JsonStockData</servlet-class>
  </servlet>

  <servlet-mapping>
    <!--====== /stockwatcher/stockPricesというURLに関連付け ======-->
    <servlet-name>jsonStockData</servlet-name>
    <url-pattern>/stockwatcher/stockPrices</url-pattern>
  </servlet-mapping>

  <!-- Default page to serve -->
  <welcome-file-list>
    <welcome-file>StockWatcher.html</welcome-file>
  </welcome-file-list>

</web-app>





ここまでで、JSONデータの取得が行えるようになっているはずです。
ブラウザで
http://localhost:8888/stockwatcher/stockPrices?q=ABC+DEF
を表示すると、こんな結果が得られると思います。

11_002_20101109002730.png







2. Manipulating JSON data in the client-side code



まず、JSONデータをラップするためのクラスを作成します。

パッケージ・エクスプローラーで、
「com.google.gwt.sample.stockwatcher.client」
を選択した状態で、Eclipseのツールバーの[ファイル]-[新規作成]-[クラス]を選択。

パッケージは「com.google.gwt.sample.stockwatcher.client」
クラス名は「StockData」
スーパークラスは「com.google.gwt.core.client.JavaScriptObject」
として、完了を押下します。

11_003.png


作成した「StockData」を以下の内容で更新します。


package com.google.gwt.sample.stockwatcher.client;

import com.google.gwt.core.client.JavaScriptObject;

public class StockData extends JavaScriptObject {
    // Overlay types always have protected, zero argument constructors.
    protected StockData() {
    }
    
    // JSNI methods to get stock data.
    public final native String getSymbol() /*-{ return this.symbol; }-*/;

    public final native double getPrice() /*-{ return this.price; }-*/;

    public final native double getChange() /*-{ return this.change; }-*/;

    // Non-JSNI method to return change percentage. // [4]
    public final double getChangePercent() {
        return 100.0 * getChange() / getPrice();
    }
}





このクラスが、サーバーから取得したデータの

  {
    "symbol": "DEF",
    "price": 43.819500786906815,
    "change": 0.18992536537119453
  }


この部分に割り当てられるイメージでよいかな?と思っています。





3. Making HTTP requests



まず、StockWatcher.gwt.xmlに変更を加えます。


<?xml version="1.0" encoding="UTF-8"?>
<module rename-to='stockwatcher'>
<!-- Inherit the core Web Toolkit stuff.                        -->
<inherits name='com.google.gwt.user.User'/>

<!-- Inherit the default GWT style sheet. You can change     -->
<!-- the theme of your GWT application by uncommenting         -->
<!-- any one of the following lines.                            -->
<inherits name='com.google.gwt.user.theme.standard.Standard'/>
<!-- <inherits name='com.google.gwt.user.theme.chrome.Chrome'/> -->
<!-- <inherits name='com.google.gwt.user.theme.dark.Dark'/>     -->

<!-- Other module inherits                                     -->
<!--=== 以下の一行を追加 ===-->
<inherits name="com.google.gwt.http.HTTP" />

<!-- Specify the app entry point class.                         -->
<entry-point class='com.google.gwt.sample.stockwatcher.client.StockWatcher'/>

<!-- Specify the paths for translatable code                    -->
<source path='client'/>
<source path='shared'/>

</module>




「com.google.gwt.http.HTTP」の記載を追加することで、サーバー側との
HTTP通信が行えるようになるようです。
※雛形のJavaScriptにメソッドが追加出力されるようになる?



次に、StockWatcher.javaに以下のimportを追加。


import com.google.gwt.core.client.JsArray
import com.google.gwt.core.client.GWT;
import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.http.client.Response;
import com.google.gwt.http.client.URL;
import java.util.Iterator;





データ取得を行うURLを定義


private static final String JSON_URL = GWT.getModuleBaseURL() + "stockPrices?q=";





株価データの取得を行うメソッド「refreshWatchList」を変更します。


    // 価格情報の更新を行う
    private void refreshWatchList() {
        if (stocks.size() == 0) {
            return;
        }

        String url = JSON_URL;

        // Append watch list stock symbols to query URL.
        Iterator iter = stocks.iterator();
        while (iter.hasNext()) {
            url += iter.next();
            if (iter.hasNext()) {
                url += "+";
            }
        }

        url = URL.encode(url);

        // Send request to server and catch any errors.
        RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, url);

        try {
            Request request = builder.sendRequest(null, new RequestCallback() {
                public void onError(Request request, Throwable exception) {
                    displayError("Couldn't retrieve JSON");
                }

                public void onResponseReceived(Request request,
                        Response response) {
                    if (200 == response.getStatusCode()) {
                        updateTable(asArrayOfStockData(response.getText()));
                    } else {
                        displayError("Couldn't retrieve JSON ("
                                + response.getStatusText() + ")");
                    }
                }
            });
        } catch (RequestException e) {
            displayError("Couldn't retrieve JSON");
        }
    }






取得したJSON形式のデータを配列に変換するメソッドを追加。


    private final native JsArray<StockData> asArrayOfStockData(String json) /*-{
        return eval(json);
    }-*/;





updateTableの引数を、「StockPrice[]」から「JsArray<StockData>」に変更します。


    /*
    private void updateTable(StockPrice[] prices) {
        // テーブルの更新処理
        for (int i = 0; i < prices.length; i++) {
            updateTable(prices[i]);
        }
    */
    private void updateTable(JsArray<StockData> prices) {
        // テーブルの更新処理
        for (int i = 0; i < prices.length(); i++) {
            updateTable(prices.get(i));
        }
        
        DateTimeFormat timeFormat = DateTimeFormat.getFormat("yyyy年MM月dd日 HH:mm:ss");
        
        // 最終更新時間を表示
        lastUpdatedLabel.setText("最終更新時間 : "
            + timeFormat.format(new Date(), TimeZone.createTimeZone(-60 * 9)));
        
        // ここまで処理がくれば、エラーは発生していない
        // エラーメッセージ領域を非表示にする
        errorMsgLabel.setVisible(false);
    }





もう一箇所、updateTableの引数「StockPrice」を「StockData」に変更。


    //private void updateTable(StockPrice price) {
    private void updateTable(StockData price) {
        // ArrayListに指定された株式銘柄コードが存在しない場合は処理中断
        if (!stocks.contains(price.getSymbol())) {
            return;
        }








4. Handling GET errors



エラーメッセージ表示用のメソッド「displayError」を定義します。


    // エラー表示
    private void displayError(String error) {
        errorMsgLabel.setText("Error: " + error);
        errorMsgLabel.setVisible(true);
    }






駆け足でしたが、これでGWT RPCからJSONへの変更は完了です。

開発用のサーバーを起動して、ブラウザからアクセスすると従来どおりの
表示が行われているはず。


11_004.png




最終的に、StockWatcher.javaはこうなりました。


package com.google.gwt.sample.stockwatcher.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.user.client.Window;
import java.util.ArrayList;
import com.google.gwt.user.client.Timer;
import com.google.gwt.i18n.client.NumberFormat;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.i18n.client.TimeZone;

import java.util.Date;
import com.google.gwt.core.client.GWT;

import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.http.client.Response;
import com.google.gwt.http.client.URL;
import java.util.Iterator;

public class StockWatcher implements EntryPoint {
    
    // 更新間隔を指定
    private static final int REFRESH_INTERVAL = 5000; // ms
    private VerticalPanel mainPanel = new VerticalPanel();
    private FlexTable stocksFlexTable = new FlexTable();
    private HorizontalPanel addPanel = new HorizontalPanel();
    private TextBox newSymbolTextBox = new TextBox();
    private Button addStockButton = new Button("追加");
    private Label lastUpdatedLabel = new Label();
    // テーブルに表示しているデータ保存用の変数
    private ArrayList<String> stocks = new ArrayList<String>();
    private StockPriceServiceAsync stockPriceSvc = GWT.create(StockPriceService.class);
    
    private Label errorMsgLabel = new Label();
    
    private static final String JSON_URL = GWT.getModuleBaseURL() + "stockPrices?q=";
    
    /**
     * Entry point method.
     */
    public void onModuleLoad() {
        // テーブルの作成
        stocksFlexTable.setText(0, 0, "銘柄");
        stocksFlexTable.setText(0, 1, "価格");
        stocksFlexTable.setText(0, 2, "変動");
        stocksFlexTable.setText(0, 3, "削除");
        
        // CellPaddingを指定して、間隔を
        stocksFlexTable.setCellPadding(6);
        
        // スタイルの指定を追加
        stocksFlexTable.getRowFormatter().addStyleName(0, "watchListHeader");
        stocksFlexTable.addStyleName("watchList");
        stocksFlexTable.getCellFormatter().addStyleName(0, 1, "watchListNumericColumn");
        stocksFlexTable.getCellFormatter().addStyleName(0, 2, "watchListNumericColumn");
        stocksFlexTable.getCellFormatter().addStyleName(0, 3, "watchListRemoveColumn");
        
        // 部品が横に並ぶパネルに、銘柄入力テキストとボタンを追加
        addPanel.add(newSymbolTextBox);
        addPanel.add(addStockButton);
        // スタイルの指定を追加
        addPanel.addStyleName("addPanel");
        
        // エラーメッセージ表示領域
        errorMsgLabel.setStyleName("errorMessage");
        errorMsgLabel.setVisible(false);
        
        // 部品が縦に並ぶパネルに、エラー表示ラベル、テーブルと上記パネル、
        // 最終更新日ラベルを追加
        mainPanel.add(errorMsgLabel);
        mainPanel.add(stocksFlexTable);
        mainPanel.add(addPanel);
        mainPanel.add(lastUpdatedLabel);
        
        // StockWatcher.htmlのid=stockListのdivを
        // ルートパネルとして取得し、mainPanelを設定する。
        RootPanel.get("stockList").add(mainPanel);
        
        // 起動時、銘柄入力のテキストボックスにフォーカスを設定する
        newSymbolTextBox.setFocus(true);
        
        // 追加ボタンにイベントリスナーを設定
        addStockButton.addClickHandler(new ClickHandler() {
            public void onClick(ClickEvent event) {
                addStock();
            }
        });
        
        // 銘柄入力テキストにイベントリスナーを設定
        newSymbolTextBox.addKeyPressHandler(new KeyPressHandler() {
            public void onKeyPress(KeyPressEvent event) {
                // 入力されたキーが「Enter」だったら、追加処理を実行する
                if (event.getCharCode() == KeyCodes.KEY_ENTER) {
                    addStock();
                }
            }
        });
        
        // データの自動更新を行うために、onModuleLoadメソッドでタイマーを生成
        Timer refreshTimer = new Timer() {
            @Override
            public void run() {
                refreshWatchList();
            }
        };
        refreshTimer.scheduleRepeating(REFRESH_INTERVAL);
    }
    
    // 株式銘柄の追加処理
    private void addStock() {
        // テキストボックスから入力された文字列を取得
        final String symbol = newSymbolTextBox.getText().toUpperCase().trim();
        newSymbolTextBox.setFocus(true);

        // 証券コードは、1文字以上10文字以下
        // 使える文字は数字・アルファベット・ドット(.)のみ
        if (!symbol.matches("^[0-9A-Z\\.]{1,10}$")) {
            // 入力された文字が証券コードではない場合は、エラーメッセージ表示
            Window.alert("'" + symbol + "' は不正な証券コードです。");
            newSymbolTextBox.selectAll();
            return;
        }

        newSymbolTextBox.setText("");
        
        // 証券コードの重複チェック処理
        // 追加予定の証券コードが既にリストに含まれていたら、追加処理を実行しない。
        if (stocks.contains(symbol)) {
            return;
        }
        // テーブルへの行追加処理
        // 現在のテーブルの行数を取得
        int row = stocksFlexTable.getRowCount();
        // ArrayListに追加した証券コードを退避
        stocks.add(symbol);
        // テーブルへ行の追加実行
        stocksFlexTable.setText(row, 0, symbol);
        // ラベルを追加
        stocksFlexTable.setWidget(row, 2, new Label());
        
        // スタイルの指定を追加
        stocksFlexTable.getCellFormatter().addStyleName(row, 1, "watchListNumericColumn");
        stocksFlexTable.getCellFormatter().addStyleName(row, 2, "watchListNumericColumn");
        stocksFlexTable.getCellFormatter().addStyleName(row, 3, "watchListRemoveColumn");
        
        // 追加した行に削除ボタンを配置
        Button removeStockButton = new Button("x");
        removeStockButton.addStyleDependentName("remove");
        
        // 削除ボタンが押されたときのイベントを定義
        removeStockButton.addClickHandler(new ClickHandler() {
            public void onClick(ClickEvent event) {
                // 削除する行のインデックスを取得
                int removedIndex = stocks.indexOf(symbol);
                // ArrayListから削除
                stocks.remove(removedIndex);
                // テーブルからも削除
                stocksFlexTable.removeRow(removedIndex + 1);
            }
        });
        
        // 削除ボタンをテーブルに追加
        stocksFlexTable.setWidget(row, 3, removeStockButton);
        
        // 証券の金額を取得
        refreshWatchList();
    }
    
    // 価格情報の更新を行う
    private void refreshWatchList() {
        if (stocks.size() == 0) {
            return;
        }

        String url = JSON_URL;

        // Append watch list stock symbols to query URL.
        Iterator iter = stocks.iterator();
        while (iter.hasNext()) {
            url += iter.next();
            if (iter.hasNext()) {
                url += "+";
            }
        }

        url = URL.encode(url);

        // Send request to server and catch any errors.
        RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, url);

        try {
            Request request = builder.sendRequest(null, new RequestCallback() {
                public void onError(Request request, Throwable exception) {
                    displayError("Couldn't retrieve JSON");
                }

                public void onResponseReceived(Request request,
                        Response response) {
                    if (200 == response.getStatusCode()) {
                        updateTable(asArrayOfStockData(response.getText()));
                    } else {
                        displayError("Couldn't retrieve JSON ("
                                + response.getStatusText() + ")");
                    }
                }
            });
        } catch (RequestException e) {
            displayError("Couldn't retrieve JSON");
        }
    }

    private final native JsArray<StockData> asArrayOfStockData(String json) /*-{
        return eval(json);
    }-*/;
    
    /*
    private void updateTable(StockPrice[] prices) {
        // テーブルの更新処理
        for (int i = 0; i < prices.length; i++) {
            updateTable(prices[i]);
        }
    */
    private void updateTable(JsArray<StockData> prices) {
        // テーブルの更新処理
        for (int i = 0; i < prices.length(); i++) {
            updateTable(prices.get(i));
        }
        
        DateTimeFormat timeFormat = DateTimeFormat.getFormat("yyyy年MM月dd日 HH:mm:ss");
        
        // 最終更新時間を表示
        lastUpdatedLabel.setText("最終更新時間 : "
            + timeFormat.format(new Date(), TimeZone.createTimeZone(-60 * 9)));
        
        // ここまで処理がくれば、エラーは発生していない
        // エラーメッセージ領域を非表示にする
        errorMsgLabel.setVisible(false);
    }
    
    //private void updateTable(StockPrice price) {
    private void updateTable(StockData price) {
        // ArrayListに指定された株式銘柄コードが存在しない場合は処理中断
        if (!stocks.contains(price.getSymbol())) {
            return;
        }

        int row = stocks.indexOf(price.getSymbol()) + 1;

        // 価格表示用のフォーマット
        String priceText = NumberFormat.getFormat("#,##0.00").format(
                price.getPrice());
        // 変動率表示用のフォーマット
        NumberFormat changeFormat = NumberFormat
                .getFormat("+#,##0.00;-#,##0.00");
        
        // 価格・変動率をフォーマットした文字列を取得
        String changeText = changeFormat.format(price.getChange());
        String changePercentText = changeFormat
                .format(price.getChangePercent());

        // 価格・変動率のテーブル表示を更新
        stocksFlexTable.setText(row, 1, priceText);
        // ラベルに対してテキストを追加するようにする。
        Label changeWidget = (Label)stocksFlexTable.getWidget(row, 2);
        changeWidget.setText(changeText + " (" + changePercentText + "%)");
        
        // 変動率に応じて、適用するスタイルの名称を変更
        String changeStyleName = "noChange";
        if (price.getChangePercent() < -0.1f) {
         changeStyleName = "negativeChange";
        }
        else if (price.getChangePercent() > 0.1f) {
         changeStyleName = "positiveChange";
        }
        
        changeWidget.setStyleName(changeStyleName);
        
    }
    
    // エラー表示
    private void displayError(String error) {
        errorMsgLabel.setText("Error: " + error);
        errorMsgLabel.setVisible(true);
    }
}









テーマ:プログラミング - ジャンル:コンピュータ

  1. 2010/11/09(火) 00:29:51|
  2. Google Web Toolkit
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集

Google Web Toolkit 2.1 GWT RPCの使用

Google Web Toolkit 2.1のチュートリアルを試してみています。
Google Web Toolkit に関する記事の一覧


簡単なアプリケーションを作成しましたが、クライアント側の
JavaScriptの動作で完結する内容でした。

実際は、サーバーと通信してデータの取得や登録を行う
アプリケーションが多いと思いますので、RPCの使用に関する
章も試してみることにします。


Making Remote Procedure Calls
こちらを参考に、プログラムを作成しました。




1. Creating a service



これまでのチュートリアルで作成した「StockWatcher」で、株価の値は
StockWatcherクラスのrefreshWatchListメソッドでランダムに生成していました。

該当する処理の箇所はここ。


    // 価格情報の更新を行う
    private void refreshWatchList() {
        // 価格情報の更新処理
        final double MAX_PRICE = 100.0; // $100.00
        final double MAX_PRICE_CHANGE = 0.02; // +/- 2%

        StockPrice[] prices = new StockPrice[stocks.size()];
        for (int i = 0; i < stocks.size(); i++) {
            double price = Random.nextDouble() * MAX_PRICE;
            double change = price * MAX_PRICE_CHANGE
                    * (Random.nextDouble() * 2.0 - 1.0);

            prices[i] = new StockPrice(stocks.get(i), price, change);
        }

        updateTable(prices);
        
    }





この部分をサーバー側で処理するように変更します。
まずは、「RemoteService」を継承して「StockPriceService」を作成します。

パッケージ・エクスプローラーで「com.google.gwt.sample.stockwatcher.client」を
選択しておきます。

10_001_20101107191601.png


Eclipseツールバーの[ファイル]-[新規]-[インターフェース]を選択。

10_002_20101107191601.png


名前に「StockPriceService」を入力して「完了」を押します。
その他はデフォルトのままでOK

10_003_20101107191601.png


StockPriceService.javaを以下の内容に変更します。
※変更した時点ではエラーが表示されると思いますが、次のソースを
作成することでエラーが解消されますので、一旦無視で。


package com.google.gwt.sample.stockwatcher.client;

import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;

@RemoteServiceRelativePath("stockPrices")
public interface StockPriceService extends RemoteService {
    StockPrice[] getPrices(String[] symbols);
}





次に、「RemoteServiceServlet」を継承したクラスを作成します。

Eclipseのツールバーから[ファイル]-[新規]-[クラス]を選択。

10_004_20101107191601.png


パッケージ名を
「com.google.gwt.sample.stockwatcher.client」から
「com.google.gwt.sample.stockwatcher.server」に変更します。

名前は「StockPriceServiceImpl」

スーパークラスに「com.google.gwt.user.server.rpc.RemoteServiceServlet」を
入力します。

10_005.png


インターフェースは右側にある追加ボタンを押して、
「com.google.gwt.sample.stockwatcher.client.StockPriceService」
を入力し候補を表示。OKを押します。

10_006.png


インターフェースの項目に、指定したクラスが追加されたことを確認して、
完了ボタンを押下します。

10_007.png


Eclipseで自動生成された「StockPriceServiceImpl.java」は
以下のようになるはずです。



package com.google.gwt.sample.stockwatcher.server;

import com.google.gwt.sample.stockwatcher.client.StockPrice;
import com.google.gwt.sample.stockwatcher.client.StockPriceService;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

public class StockPriceServiceImpl extends RemoteServiceServlet implements
        StockPriceService {

    @Override
    public StockPrice[] getPrices(String[] symbols) {
        // TODO 自動生成されたメソッド・スタブ
        return null;
    }

}





この「StockPriceServiceImpl」クラスの「getPrices」メソッドに
クライアントのJavaScriptで動作していた株価生成の処理を移動します。

処理記載後のStockPriceServiceImpl.javaはこうなりました。


package com.google.gwt.sample.stockwatcher.server;

import com.google.gwt.sample.stockwatcher.client.StockPrice;
import com.google.gwt.sample.stockwatcher.client.StockPriceService;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

import java.util.Random;

public class StockPriceServiceImpl extends RemoteServiceServlet implements
        StockPriceService {
    
    private static final long serialVersionUID = 1L;
    private static final double MAX_PRICE = 100.0; // $100.00
    private static final double MAX_PRICE_CHANGE = 0.02; // +/- 2%
    
    @Override
    public StockPrice[] getPrices(String[] symbols) {
        Random rnd = new Random();
        
        StockPrice[] prices = new StockPrice[symbols.length];
        for (int i=0; i<symbols.length; i++) {
            double price = rnd.nextDouble() * MAX_PRICE;
            double change = price * MAX_PRICE_CHANGE * (rnd.nextDouble() * 2f - 1f);
            
            prices[i] = new StockPrice(symbols[i], price, change);
        }
        return prices;
    }

}







この作成したサービスはservletで動作します。
そのため、web.xmlへservletの追加が必要になります。

http://localhost:8888/stockwatcher/stockPrices

というURLでサービスを公開しようと思いますので、
StockWatcher/war/WEB-INF/web.xml
を以下のように編集しておきます。

※雛形としてで出力されている「greetServlet」という定義があるかと
思いますが、ここで消してしまいます。


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>

  <!-- Servlets -->
  <servlet>
    <!--====== stockPriceServiceImplというサーブレットを定義 ======-->
    <servlet-name>stockPriceServiceImpl</servlet-name>
    <servlet-class>com.google.gwt.sample.stockwatcher.server.StockPriceServiceImpl</servlet-class>
  </servlet>

  <servlet-mapping>
    <!--====== /stockwatcher/stockPricesというURLに関連付け ======-->
    <servlet-name>stockPriceServiceImpl</servlet-name>
    <url-pattern>/stockwatcher/stockPrices</url-pattern>
  </servlet-mapping>

  <!-- Default page to serve -->
  <welcome-file-list>
    <welcome-file>StockWatcher.html</welcome-file>
  </welcome-file-list>

</web-app>









2. Invoking the service from the client



ここまでの操作でサーバー側で実行されるプログラムが作成できたことになります。

これから、クライアント側からサーバー側に通信するための
プログラムを作成します。

前の段落で作成した「StockPriceService」ですが、エラーが表示されて
いる状態だと思います。

ソースの左側にある黄色いアイコンをクリックすると、こんな感じで
修正候補が表示されるはずです。

10_008.png


一番先頭に表示される「Create asynchronous RemoteService interface ...」
を選択すると、ダイアログが表示されます。

そのまま完了を押下して、操作を進めます。

10_009.png


自動生成された「StockPriceServiceAsync」は以下のようになるはずです。



package com.google.gwt.sample.stockwatcher.client;

import com.google.gwt.user.client.rpc.AsyncCallback;

public interface StockPriceServiceAsync {

    void getPrices(String[] symbols, AsyncCallback<StockPrice[]> callback);

}






クライアント側では、このクラスを経由してサーバー側との通信を
実現する模様。

StockWatcherクラスに以下のimportを追加。

import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.rpc.AsyncCallback;




また、以下の変数も追加。


private StockPriceServiceAsync stockPriceSvc = GWT.create(StockPriceService.class);




refreshWatchListメソッドの処理内容を
このように変更します。


    // 価格情報の更新を行う
    private void refreshWatchList() {
        // Initialize the service proxy.
        if (stockPriceSvc == null) {
            stockPriceSvc = GWT.create(StockPriceService.class);
        }
        
        // Set up the callback object.
        AsyncCallback<StockPrice[]> callback = new AsyncCallback<StockPrice[]>() {
            public void onFailure(Throwable caught) {
                // TODO: Do something with errors.
            }
            
            public void onSuccess(StockPrice[] result) {
                updateTable(result);
            }
        };
        
        // Make the call to the stock price service.
        stockPriceSvc.getPrices(stocks.toArray(new String[0]), callback);
    }





ここまでできたら、開発用のサーバーを起動し、ブラウザでアクセスしてみます。
すると、こんな感じでエラーが表示されるはずです。

10_010.png



18:37:31.126 [ERROR] [stockwatcher]
subtype com.google.gwt.sample.stockwatcher.client.StockPrice is not
assignable to 'com.google.gwt.user.client.rpc.IsSerializable' or
'java.io.Serializable' nor does it have a custom field serializer
(reached via com.google.gwt.sample.stockwatcher.client.StockPrice[])




クライアントとサーバー間のデータのやり取りに使用している
「StockPrice」をシリアライズできなかったよ。というエラーですね。






3. Serializing Java objects



というわけで、データの通信に使用している「StockPrice」を
シリアライズ可能とするため、「Serializable」をimplementsするよう
変更してやります。

StockPriceで変更した箇所の抜粋はこちら。


package com.google.gwt.sample.stockwatcher.client;

// シリアライズするため、importを追加
import java.io.Serializable;

public class StockPrice implements Serializable {

    private static final long serialVersionUID = 1L;
    private String symbol;
    private double price;
    private double change;







4. Handling Exceptions



最後にエラー処理を追加します。

Eclipseのツールバー[ファイル]-[新規]-[クラス]を選択。

名前「DelistedException」
スーパークラス「java.lang.Exception」
インターフェース「java.io.Serializable」
を入力し、完了を押下。

10_011.png


DelistedExceptionを以下の内容に変更します。


package com.google.gwt.sample.stockwatcher.client;

import java.io.Serializable;

public class DelistedException extends Exception implements Serializable {

    private static final long serialVersionUID = 1L;
    private String symbol;

    public DelistedException() {
    }

    public DelistedException(String symbol) {
        this.symbol = symbol;
    }

    public String getSymbol() {
        return this.symbol;
    }
}






StockPriceServiceのgetPricesメソッドが作成した例外を
投げられるように修正します。


package com.google.gwt.sample.stockwatcher.client;

import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;

@RemoteServiceRelativePath("stockPrices")
public interface StockPriceService extends RemoteService {
    StockPrice[] getPrices(String[] symbols) throws DelistedException;;
}







エラー発生のサンプルとして、StockPriceServiceImplの株価を取得する処理で、
株式銘柄コードに「ERR」と入力された場合は、DelistedExceptionを
発生するように修正しました。

修正後の「StockPriceServiceImpl」はこんな感じになります。


package com.google.gwt.sample.stockwatcher.server;

import com.google.gwt.sample.stockwatcher.client.StockPrice;
import com.google.gwt.sample.stockwatcher.client.StockPriceService;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

import java.util.Random;
import com.google.gwt.sample.stockwatcher.client.DelistedException;

public class StockPriceServiceImpl extends RemoteServiceServlet implements
        StockPriceService {
    
    private static final long serialVersionUID = 1L;
    private static final double MAX_PRICE = 100.0; // $100.00
    private static final double MAX_PRICE_CHANGE = 0.02; // +/- 2%
    
    @Override
    public StockPrice[] getPrices(String[] symbols) throws DelistedException {
        Random rnd = new Random();
        
        StockPrice[] prices = new StockPrice[symbols.length];
        for (int i=0; i<symbols.length; i++) {
            
            // 動作サンプルとして、銘柄コードに「ERR」と入力されている場合は
            // 例外を発生させる
            if (symbols[i].equals("ERR")) {
                throw new DelistedException("ERR");
            }
            
            double price = rnd.nextDouble() * MAX_PRICE;
            double change = price * MAX_PRICE_CHANGE * (rnd.nextDouble() * 2f - 1f);
            
            prices[i] = new StockPrice(symbols[i], price, change);
        }
        return prices;
    }

}









エラーの表示は赤い文字で行いたいと思いますので、
StockWatcher.cssに以下の記載を追加。


/* エラー色を指定 */
.errorMessage {
  color: red;
}





StockWatcher.javaにエラー表示用のラベルを追加します。


    private Label errorMsgLabel = new Label();




作成したラベルオブジェクトをパネルに配置する処理も追加。


        // 部品が横に並ぶパネルに、銘柄入力テキストとボタンを追加
        addPanel.add(newSymbolTextBox);
        addPanel.add(addStockButton);
        // スタイルの指定を追加
        addPanel.addStyleName("addPanel");
        
        // エラーメッセージ表示領域
        errorMsgLabel.setStyleName("errorMessage");
        errorMsgLabel.setVisible(false);
        
        // 部品が縦に並ぶパネルに、エラー表示ラベル、テーブルと上記パネル、
        // 最終更新日ラベルを追加
        mainPanel.add(errorMsgLabel);
        mainPanel.add(stocksFlexTable);
        mainPanel.add(addPanel);
        mainPanel.add(lastUpdatedLabel);






refreshWatchListメソッドでTODOとして残していた、onFailureの処理を追記。
サーバー側で発生したエラーをラベルに出力します。


    // 価格情報の更新を行う
    private void refreshWatchList() {
        // Initialize the service proxy.
        if (stockPriceSvc == null) {
            stockPriceSvc = GWT.create(StockPriceService.class);
        }
        
        // Set up the callback object.
        AsyncCallback<StockPrice[]> callback = new AsyncCallback<StockPrice[]>() {
            public void onFailure(Throwable caught) {
                String details = caught.getMessage();
                if (caught instanceof DelistedException) {
                    details = "Company '" + ((DelistedException)caught).getSymbol() + "' was delisted";
                }
                errorMsgLabel.setText("Error: " + details);
                errorMsgLabel.setVisible(true);
            }
            
            public void onSuccess(StockPrice[] result) {
                updateTable(result);
            }
        };
        
        // Make the call to the stock price service.
        stockPriceSvc.getPrices(stocks.toArray(new String[0]), callback);
    }






このままだと、一度エラーが発生した後はずっとエラーの表示が
残ってしまうため、エラー表示を消去する処理を追加します。


    private void updateTable(StockPrice[] prices) {
        // テーブルの更新処理
        for (int i = 0; i < prices.length; i++) {
            updateTable(prices[i]);
        }
        
        DateTimeFormat timeFormat = DateTimeFormat.getFormat("yyyy年MM月dd日 HH:mm:ss");
        
        // 最終更新時間を表示
        lastUpdatedLabel.setText("最終更新時間 : "
            + timeFormat.format(new Date(), TimeZone.createTimeZone(-60 * 9)));
        
        // ここまで処理がくれば、エラーは発生していない
        // エラーメッセージ領域を非表示にする
        errorMsgLabel.setVisible(false);
    }






開発用のサーバーを起動し、ブラウザで動作を確認してみると、
狙い通りの動きになっているかと思います。

10_012.png




関連記事



Google Web Toolkit に関する記事の一覧









テーマ:プログラミング - ジャンル:コンピュータ

  1. 2010/11/07(日) 19:19:02|
  2. Google Web Toolkit
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集
次のページ