Google App Engine for Java: 第 2 回 キラー・アプリケーションを構築する

App Engine で独自の連絡先管理アプリケーションを構築する

Google App Engine for Java™ のようなクラウド・プラットフォームの本質は、プロ級の品質を備えたスケーラブルなキラー・アプリケーションを考え出し、構築し、デプロイできるところにあります。しかも、混乱に陥ることもなく、あまり費用をかけずに実現することができます。Google App Engine for Java を紹介するこの 3 回からなる連載の第 2 回では、第 1 回で取り上げた既製のサンプル・アプリケーションの枠を超え、App Engine for Java を使って単純な連絡先管理アプリケーションを作成し、デプロイするプロセスを手順に沿って説明します。

Richard Hightower, CTO, Mammatus Inc.

Rick Hightower は、クラウド・コンピューティング、GWT、Java EE、Spring、そしてHibernate 開発のトレーニングを専門とする会社、Mammatus Inc. の最高技術責任者です。人気の高い『Java Tools for Extreme Programming』の著者であり、長年 TheServerSide.com でのダウンロード件数第 1 位となっている『Struts Live』初版の共著者でもあります。また、IBM developerWorks にも記事、チュートリアルを投稿している他、Java Developer's Journal の編集委員を務め、DZone では Java および Groovy に関するトピックのコントリビューターとしてお馴染みです。



2009年 8月 11日

この連載では、App Engine for Java を使用してスケーラブルな Java アプリケーションを構築する方法を紹介しますが、その第 1 回では Google のクラウド・コンピューティング・プラットフォーム (PaaS) が Java 開発者向けに用意している Eclipse ツールとインフラストラクチャーについて学びました。前回の記事で取り上げたサンプルは既製のものだったので、App Engine for Java と Eclipse の統合に専念することができ、異なるタイプのアプリケーション (具体的には、GWT (Google Web Toolkit) で作成したアプリケーションと、サーブレット・ベースのアプリケーション) を作成してデプロイする演習を簡単に行うことができました。今回の記事では、前回で学んだ基礎を踏まえ、第 3 回で行う高度なプログラミングの演習に備えます。

今回作成するのは、ユーザーが基本的な連絡先情報 (名前、E メール・アドレス、電話番号) を保存できる連絡先管理アプリケーションです。このアプリケーションを作成するには、Eclipse GWT プロジェクト作成ウィザードを使用します。

CRUD アプリケーションから連絡先管理アプリケーションへの拡張

App Engine for Java で新規アプリケーションを作成する際に最初のステップとなるのは、すでにご存知のとおり、Eclipse でプロジェクトを作成するためのウィザードを起動することです。ウィザードが起動したら、GWT プロジェクトを開始するためのウィザードを立ち上げて GWT プロジェクトの作成に取り掛かります (App Engine for Java で GWT プロジェクトを作成する方法については、連載第 1 回で詳しく説明したとおりです)。

この演習では、まずは単純な CRUD アプリケーションから開始し、その後で実際のストレージを追加していきます。とりあえずは、リスト 1 に記載するモックの実装が含まれるデータ・アクセス・オブジェクト (DAO) を使用してください。

リスト 1. ContactDAO 用のインターフェース
package gaej.example.contact.server;

import java.util.List;

import gaej.example.contact.client.Contact;

public interface ContactDAO {
    void addContact(Contact contact);
    void removeContact(Contact contact);
    void updateContact(Contact contact);
    List<Contact> listContacts();
}

ContactDAO は、連絡先の追加、削除、更新を行うメソッド、そしてすべての連絡先のリストを返すためのメソッドを追加します。これは、連絡先を管理するための極めて基本的な CRUD インターフェースです。ここでは、Contact クラスがドメイン・オブジェクトとなります (リスト 2 を参照)。

リスト 2. Contact ドメイン・オブジェクト (gaej.example.contact.client.Contact)
package gaej.example.contact.client;

import java.io.Serializable;

public class Contact implements Serializable {
    
    private static final long serialVersionUID = 1L;
    private String name;
    private String email;
    private String phone;

    public Contact() {
        
    }
    
    public Contact(String name, String email, String phone) {
        super();
        this.name = name;
        this.email = email;
        this.phone = phone;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    }
    public String getPhone() {
        return phone;
    }
    public void setPhone(String phone) {
        this.phone = phone;
    }
        

}

アプリケーションのこの最初のバージョンでは、連絡先をメモリー内のコレクションに保存するモック・オブジェクトを扱います (リスト 3 を参照)。

リスト 3. モック DAO クラス
package gaej.example.contact.server;

import gaej.example.contact.client.Contact;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class ContactDAOMock implements ContactDAO {

    Map<String, Contact> map = new LinkedHashMap<String, Contact>();
    
    {
        map.put("rhightower@mammatus.com", 
          new Contact("Rick Hightower", "rhightower@mammatus.com", "520-555-1212"));
        map.put("scott@mammatus.com", 
          new Contact("Scott Fauerbach", "scott@mammatus.com", "520-555-1213"));
        map.put("bob@mammatus.com", 
          new Contact("Bob Dean", "bob@mammatus.com", "520-555-1214"));


    }
   
    public void addContact(Contact contact) {
        String email = contact.getEmail();
        map.put(email, contact);
    }

    public List<Contact> listContacts() {
        return Collections.unmodifiableList(new ArrayList<Contact>(map.values()));
    }

    public void removeContact(Contact contact) {
        map.remove(contact.getEmail());
    }

    public void updateContact(Contact contact) {        
        map.put(contact.getEmail(), contact);
    }

}

リモート・サービスを作成する

ここで目的とするのは、DAO を使用できるようにする GWT GUI の作成です。GWT GUI は、ContactDAO インターフェースのすべてのメソッドを使用する必要があります。そこで最初のステップとして、DAO クラスの機能 (以降のバージョンは、サーバー・サイドのデータ・ストレージと実際に対話することになるため、この機能はサーバー上になければなりません) を 1 つのサービスにラップします (リスト 4 を参照)。

リスト 4. ContactServiceImpl
package gaej.example.contact.server;

import java.util.ArrayList;
import java.util.List;

import gaej.example.contact.client.Contact;
import gaej.example.contact.client.ContactService;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;

public class ContactServiceImpl extends RemoteServiceServlet implements ContactService {
    private static final long serialVersionUID = 1L;
    private ContactDAO contactDAO = new ContactDAOMock();

    public void addContact(Contact contact) {
        contactDAO.addContact(contact);
    }

    public List<Contact> listContacts() {
        List<Contact> listContacts = contactDAO.listContacts();
        return new ArrayList<Contact> (listContacts);
    }

    public void removeContact(Contact contact) {
        contactDAO.removeContact(contact);
    
    }

    public void updateContact(Contact contact) {
        contactDAO.updateContact(contact);
    }
    

}

ContactServiceImpl では、まず RemoteServiceServlet を実装してから、連絡先の追加、削除、更新をするためのメソッドや、連絡先のリストを作成するためのメソッドを定義していることに注意してください。これらの操作はすべて、ContactDAOMock に委譲されます。ContactServiceImpl は、ContactDAO の機能を GWT GUI に公開する ContactDAO のラッパーにすぎません。ContactServiceImpl は、web.xml ファイル内で URI /contactlist/contacts にマッピングされます。

リスト 5. ContactService の web.xml
  <servlet>
    <servlet-name>contacts</servlet-name>
    <servlet-class>gaej.example.contact.server.ContactServiceImpl</servlet-class>
  </servlet>
 
  <servlet-mapping>
    <servlet-name>contacts</servlet-name>
    <url-pattern>/contactlist/contacts</url-pattern>
  </servlet-mapping>

GUI フロントエンドがこのサービスにアクセスできるようにするには、リモート・サービス・インターフェースと非同期リモート・サービス・インターフェースの両方を定義する必要があります (リスト 6、リスト 7 を参照)。

リスト 6. ContactService
package gaej.example.contact.client;

import java.util.List;

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

@RemoteServiceRelativePath("contacts")
public interface ContactService extends RemoteService {
    List<Contact> listContacts();
    void addContact(Contact contact);
    void removeContact(Contact contact);
    void updateContact(Contact contact);
}
リスト 7. ContactServiceAsync
package gaej.example.contact.client;

import java.util.List;

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

public interface ContactServiceAsync  {
    void listContacts(AsyncCallback<List <Contact>> callback);
    void addContact(Contact contact, AsyncCallback<Void> callback);
    void removeContact(Contact contact, AsyncCallback<Void> callback);
    void updateContact(Contact contact, AsyncCallback<Void> callback);
}

注意しなければならないのは、ContactService では RemoteService インターフェースを実装し、「連絡先」の相対パスを指定する @RemoteServiceRelativePath を定義している点です。この相対パスは、web.xml ファイル内でサービスに対して定義したパスに相当します (そのため、この 2 つのパスは一致していなければなりません)。ContactServiceAsync にはコールバック・オブジェクトがあるため、他のクライアント・アクティビティーをブロックすることなく、GWT GUI にサーバーからの呼び出しを通知することができます。

スパゲッティー・コードでないようにする

私は、スパゲッティー・コードを特に好んでいるわけではないので、可能な限り、スパゲッティー・コードを作成しないようにしています。スパゲッティー・コードの一例は、一連の匿名内部クラスがあり、これらのクラスのメソッドがさらに匿名内部クラスを定義し、さらにはこれらの匿名内部クラスが内部クラスにインラインで定義されたメソッドを呼び出すコールバックを行うといったようなものです。正直言って、このようにもつれあったコードは、たとえ自分で作成したものであっても、読むにも理解するにも苦しみます。そこで私が提案するのは、GWT GUI を以下の 3 つの部分に分割して多少フラットな構造にすることです。

  • ContactListEntryPoint
  • ContactServiceDelegate
  • ContactListGUI

メイン・エントリー・ポイントである ContactListEntryPoint は、GUI イベントの接続をします。ContactServiceDelegateContactService の機能をラップして、内部クラスのコールバック接続を隠します。ContactListGUI は GUI コンポーネントをすべて管理し、GUI および Service からのイベントを処理します。この ContactListGUI は、ContactServiceDelegate を使用して ContactService のリクエストを実行します。

ContactList.gwt.xml ファイル (gaej.example.contact の下にあるリソース) は entry-point 要素によって ContactListEntryPoint をアプリケーションのメイン・エントリー・ポイントとして指定します (リスト 8 を参照)。

リスト 8. ContactList.gwt.xml
<entry-point class='gaej.example.contact.client.ContactListEntryPoint'/>

ContactListEntryPoint クラスは GWT の EntryPoint インターフェース (com.google.gwt.core.client.EntryPoint) を実装します。つまり、GUI を初期化するには、このクラスが呼び出されるということです。ContactListEntryPoint が行う作業はたいしてありません。まず、ContactListGUI のインスタンスと ContactServiceDelegate のインスタンスを作成し、これらのインスタンスに互いを認識させて連携できるようにします。その上で、ContactListEntryPoint は GUI イベントの接続を行います。ContactListEntryPoint はリスト 9 のとおりです。

リスト 9. ContactListEntryPoint
package gaej.example.contact.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.ui.HTMLTable.Cell;

/**
 * Entry point classes define onModuleLoad().
 */
public class ContactListEntryPoint implements EntryPoint {
    private ContactListGUI gui;
    private ContactServiceDelegate delegate;
    
    /**
     * This is the entry point method.
     */
    public void onModuleLoad() {
        
        gui = new ContactListGUI();
        delegate = new ContactServiceDelegate();
        gui.contactService = delegate;
        delegate.gui = gui;
        gui.init();
        delegate.listContacts();
        wireGUIEvents();
                
        
    }

    private void wireGUIEvents() {
        gui.contactGrid.addClickHandler(new ClickHandler(){
            public void onClick(ClickEvent event) {
                 Cell cellForEvent = gui.contactGrid.getCellForEvent(event);
                 gui.gui_eventContactGridClicked(cellForEvent);                
            }});
        
        gui.addButton.addClickHandler(new ClickHandler(){
            public void onClick(ClickEvent event) {
                gui.gui_eventAddButtonClicked();
            }});

        gui.updateButton.addClickHandler(new ClickHandler(){
            public void onClick(ClickEvent event) {
                gui.gui_eventUpdateButtonClicked();
            }});
        
        gui.addNewButton.addClickHandler(new ClickHandler(){
            public void onClick(ClickEvent event) {
                gui.gui_eventAddNewButtonClicked();
                
            }});

    }
}

ContactListEntryPoint は、addButtonupdateButtoncontactGrid、そして addNewButton のイベントを接続することに注意してください。この接続は、ウィジェット・イベントのリスナー・インターフェースを実装する匿名内部クラスを登録することによって行われます。この手法は Swing でのイベント処理と非常によく似ています。ウィジェット・イベントの元となるのは、この後簡単に説明するように、GUI (ContactListGUI) によって作成されたウィジェットです。そのため GUI クラスには、GUI イベントに応答するための gui_eventXXX メソッドがあることに注意してください。

ContactListGUI は GUI ウィジェットを作成し、このウィジェットからのイベントに応答します。ContactListGUI は GUI イベントを、ユーザーが要求する ContactsService でのアクションに変換します。ContactListGUIContactService でメソッドを呼び出すために使用するのは、ContactServiceDelegate です。ContactServiceDelegateContactService に対する非同期インターフェースを作成し、これを使って非同期 Ajax 呼び出しを行います。そして、ContactServiceDelegate はサービスから返されたイベント (成功または失敗の結果) を ContactListGUI に通知します。リスト 10 に、ContactServiceDelegate を記載します。

リスト 10. ContactServiceDelegate のパッケージ gaej.example.contact.client
import java.util.List;

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

public class ContactServiceDelegate {
    private ContactServiceAsync contactService = GWT.create(ContactService.class);
    ContactListGUI gui;
    
    void listContacts() {
        contactService.listContacts(new AsyncCallback<List<Contact>> () {
                    public void onFailure(Throwable caught) {
                        gui.service_eventListContactsFailed(caught);
                    }
        
                    public void onSuccess(List<Contact> result) {
                        gui.service_eventListRetrievedFromService(result);
                        
                    }
        }//end of inner class
        );//end of listContacts method call.
    }
    
    void addContact(final Contact contact) {
        contactService.addContact(contact, new AsyncCallback<Void> () {
            public void onFailure(Throwable caught) {
                gui.service_eventAddContactFailed(caught);
            }

            public void onSuccess(Void result) {
                gui.service_eventAddContactSuccessful();
            }
        }//end of inner class
        );//end of addContact method call.        
    }

    void updateContact(final Contact contact) {
        contactService.updateContact(contact, new AsyncCallback<Void> () {
            public void onFailure(Throwable caught) {
                gui.service_eventUpdateContactFailed(caught);
            }

            public void onSuccess(Void result) {
                gui.service_eventUpdateSuccessful();
            }
        }//end of inner class
        );//end of updateContact method call.        
    }

    void removeContact(final Contact contact) {
        contactService.removeContact(contact, new AsyncCallback<Void> () {
            public void onFailure(Throwable caught) {
                gui.service_eventRemoveContactFailed(caught);
            }

            public void onSuccess(Void result) {
                gui.service_eventRemoveContactSuccessful();
            }
        }//end of inner class
        );//end of updateContact method call.        
    }
    
}

ContactServiceDelegate は、service_eventXXX で始まるメソッドを使って ContactListGUI にサービス・イベントを通知していることに注意してください。前述のとおり、ContactListGUI を作成した目的の 1 つは、内部クラスをネストさせることなく、比較的フラットな GUI クラス (後で読むにも、作業するにも簡単なクラス) を作成することでした。ContactListGUI はわずか 186 行の、かなり理解しやすいコードになっています。そのわずかなコードで、ContactListGUI は 9 つの GUI ウィジェットを扱うとともに、ContactServiceDelegate と連携してリストの CRUD を扱います (リスト 11 を参照)。

リスト 11. ContactListGUI の活躍
package gaej.example.contact.client;

import java.util.List;

import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.Hyperlink;
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.HTMLTable.Cell;

public class ContactListGUI {
    /* Constants. */
    private static final String CONTACT_LISTING_ROOT_PANEL = "contactListing";
    private static final String CONTACT_FORM_ROOT_PANEL = "contactForm";
    private static final String CONTACT_STATUS_ROOT_PANEL = "contactStatus";
    private static final String CONTACT_TOOL_BAR_ROOT_PANEL = "contactToolBar";
    private static final int EDIT_LINK = 3;
    private static final int REMOVE_LINK = 4;

    /* GUI Widgets */
    protected Button addButton;
    protected Button updateButton;
    protected Button addNewButton;
    protected TextBox nameField;
    protected TextBox emailField;
    protected TextBox phoneField;
    protected Label status;
    protected Grid contactGrid;
    protected Grid formGrid;
    
    /* Data model */
    private List<Contact> contacts;
    private Contact currentContact;
    protected ContactServiceDelegate contactService;

ContactListGUI は、フォームにロードされた現在の連絡先 (currentContact) と、contacts に含まれる連絡先のリストを追跡することに注意してください。図 1 に、作成された GUI に対応するウィジェットを示します。

図 1. 連絡先管理 GUI で有効なウィジェット
連絡先管理 GUI で有効なウィジェット

リスト 12 を見ると、どのようにして ContactListGUI がウィジェットと連絡先フォームを作成し、そのフォームにウィジェットを配置しているかがわかります。

リスト 12. ContactListGUI によるウィジェットの作成と配置
public class ContactListGUI {
    /* Constants. */
    private static final String CONTACT_LISTING_ROOT_PANEL = "contactListing";
    private static final String CONTACT_FORM_ROOT_PANEL = "contactForm";
    private static final String CONTACT_STATUS_ROOT_PANEL = "contactStatus";
    private static final String CONTACT_TOOL_BAR_ROOT_PANEL = "contactToolBar";
    ...
    public void init() {
        addButton = new Button("Add new contact");
        addNewButton = new Button("Add new contact");
        updateButton = new Button("Update contact");
        nameField = new TextBox();
        emailField = new TextBox();
        phoneField = new TextBox();
        status = new Label();
        contactGrid = new Grid(2,5);

        buildForm();
        placeWidgets();
    }
    
    private void buildForm() {
        formGrid = new Grid(4,3);
        formGrid.setVisible(false);
        
        formGrid.setWidget(0, 0, new Label("Name"));
        formGrid.setWidget(0, 1, nameField);

        formGrid.setWidget(1, 0, new Label("email"));
        formGrid.setWidget(1, 1, emailField);
        
        formGrid.setWidget(2, 0, new Label("phone"));
        formGrid.setWidget(2, 1, phoneField);
        
        formGrid.setWidget(3, 0, updateButton);
        formGrid.setWidget(3, 1, addButton);
        
    }

    private void placeWidgets() {
        RootPanel.get(CONTACT_LISTING_ROOT_PANEL).add(contactGrid);
        RootPanel.get(CONTACT_FORM_ROOT_PANEL).add(formGrid);
        RootPanel.get(CONTACT_STATUS_ROOT_PANEL).add(status);
        RootPanel.get(CONTACT_TOOL_BAR_ROOT_PANEL).add(addNewButton);
    }

ContactListGUIinit メソッドは、ContactListEntryPoint.onModuleLoad メソッドによって呼び出されます。この init メソッドは buildForm メソッドを呼び出して新規フォームのグリッドを作成し、そこに連絡先データを編集するためのフィールドを取り込みます。続いて init メソッドは placeWidgets メソッドを呼び出し、それによって contactGridformGridstatus、および addNewButton の各ウィジェットを、この GUI アプリケーションをホストする HTML ページに定義されたスロット (リスト 13) に配置します。

リスト 13. ContactList.html が定義するウィジェットのスロット
<h1>Contact List Example</h1>

    <table align="center">
      <tr>
        <td id="contactStatus"></td> <td id="contactToolBar"></td>
      </tr>
      <tr>
        <td id="contactForm"></td>
      </tr>
      <tr>
        <td id="contactListing"></td>
      </tr>
    </table>

連絡先 (CONTACT_LISTING_ROOT_PANEL="contactListing" など) は、HTML ページに定義された要素の ID (id="contactListing" など) に対応します。そのため、ページ設計者はアプリケーション・ウィジェットのレイアウトをさらに柔軟に制御できるというわけです。

基本的なアプリケーションの構築が完了したところで、今度は一般的な使用シナリオを検討してみましょう。

ページのロードによってリストを表示する場合

連絡先管理アプリケーションのページをロードすると、それによって最初に ContactListEntryPointonModuleLoad メソッドが呼び出されます。onModuleLoadContactServiceDelegatelistContacts メソッドを呼び出し、このメソッドがサービスの listContact メソッドを非同期で呼び出します。listContact メソッドから戻ると、ContactServiceDelegate に定義された匿名内部クラスがサービス・イベント・ハンドラー・メソッド service_eventListRetrievedFromService を呼び出します (リスト 14 を参照)。

リスト 14. listContact イベント・ハンドラーの呼び出し
public class ContactListGUI { 
  ...
  public void service_eventListRetrievedFromService(List<Contact> result) {
        status.setText("Retrieved contact list");
        this.contacts = result;
        this.contactGrid.clear();
        this.contactGrid.resizeRows(this.contacts.size());
        int row = 0;
        
        for (Contact contact : result) {
            this.contactGrid.setWidget(row, 0, new Label(contact.getName()));
            this.contactGrid.setWidget(row, 1, new Label (contact.getPhone()));
            this.contactGrid.setWidget(row, 2, new Label (contact.getEmail()));
            this.contactGrid.setWidget(row, EDIT_LINK, new Hyperlink("Edit", null));
            this.contactGrid.setWidget(row, REMOVE_LINK, new Hyperlink("Remove", null));
            row ++;
        }
    }

service_eventListRetrievedFromService イベント・ハンドラー・メソッドは、サーバーから送信された連絡先リストを保存した後、連絡先リストを表示する contactGrid をクリアします。そして、サーバーから返された連絡先リストの大きさに合わせて行数を調整します。その上で連絡先リストを繰り返し処理しながら、それぞれの連絡先の名前、電話番号、E メールのデータを各行の最初の 3 列に取り込みます。さらにユーザーが簡単に連絡先を削除および編集できるように、連絡先ごとに Edit リンクと Remove リンクを提供します。

ユーザーが既存の連絡先を編集する場合

ユーザーが連絡先リストの Edit リンクをクリックすると、gui_eventContactGridClicked が呼び出されます (リスト 15 を参照)。

リスト 15. ContactListGUI の gui_eventContactGridClicked イベント・ハンドラー・メソッド
public class ContactListGUI { 
  ...

    public void gui_eventContactGridClicked(Cell cellClicked) {
         int row = cellClicked.getRowIndex();
         int col = cellClicked.getCellIndex();
        
         Contact contact = this.contacts.get(row);
         this.status.setText("Name was " + contact.getName() + " clicked ");
        
         if (col==EDIT_LINK) {
             this.addNewButton.setVisible(false);
             this.updateButton.setVisible(true);
             this.addButton.setVisible(false);
             this.emailField.setReadOnly(true);
             loadForm(contact);
         } else if (col==REMOVE_LINK) {
             this.contactService.removeContact(contact);
         }
    }
   ...
    private void loadForm(Contact contact) {
        this.formGrid.setVisible(true);
        currentContact = contact;
        this.emailField.setText(contact.getEmail());
        this.phoneField.setText(contact.getPhone());
        this.nameField.setText(contact.getName());
    }

gui_eventContactGridClicked メソッドは、Edit リンクと Remove リンクのどちらがクリックされたのかを判断しなければなりません。この判断は、どの列がクリックされたのかを調べることによって行います。続いてこのメソッドでは、addNewButtonaddButton を非表示に、そして updateButton を表示するように設定します。updateButtonformGrid に表示することで、ユーザーが更新情報を ContactService に送信できるようにするためです。さらに emailField を読み取り専用にして、ユーザーが E メール・フィールドを編集できないようにします。次に、gui_eventContactGridClickedloadForm (リスト 15 を参照) を呼び出します。すると、このメソッドが formGrid を表示するように設定し、編集対象の連絡先を設定してから、連絡先のプロパティーを emailFieldphoneField、および nameField ウィジェットにコピーします。

ユーザーが updateButton をクリックすると、gui_eventUpdateButtonClicked イベント・ハンドラー・メソッドが呼び出されます (リスト 16 を参照)。このメソッドは、ユーザーが新しい連絡先を追加できるように addNewButton を表示し、formGrid を非表示にします。次に copyFieldDateToContact を呼び出し、emailFieldphoneField、および nameField ウィジェットそれぞれのテキストを currentContact のプロパティーにコピーします。そして ContactServiceDelegateupdateContact メソッドを呼び出して、新しく更新された連絡先をサービスに渡します。

リスト 16. ContactListGUI の gui_eventUpdateButtonClicked イベント・ハンドラー・メソッド
public class ContactListGUI { 
  ...

    public void gui_eventUpdateButtonClicked() {
        addNewButton.setVisible(true);
        formGrid.setVisible(false);
        copyFieldDateToContact();
        this.contactService.updateContact(currentContact);
    }
    private void copyFieldDateToContact() {
        currentContact.setEmail(emailField.getText());
        currentContact.setName(nameField.getText());
        currentContact.setPhone(phoneField.getText());
    }

以上の 2 つのシナリオから、このアプリケーションがどのように機能するか、そして App Engine for Java が提供するインフラストラクチャーをどのように利用するかがわかったはずです。リスト 17 に、ContactListGUI の完全なコードを記載します。

リスト 17. ContactListGUI の完全なコード
package gaej.example.contact.client;

import java.util.List;

import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.Hyperlink;
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.HTMLTable.Cell;

public class ContactListGUI {
    /* Constants. */
    private static final String CONTACT_LISTING_ROOT_PANEL = "contactListing";
    private static final String CONTACT_FORM_ROOT_PANEL = "contactForm";
    private static final String CONTACT_STATUS_ROOT_PANEL = "contactStatus";
    private static final String CONTACT_TOOL_BAR_ROOT_PANEL = "contactToolBar";
    private static final int EDIT_LINK = 3;
    private static final int REMOVE_LINK = 4;

    /* GUI Widgets */
    protected Button addButton;
    protected Button updateButton;
    protected Button addNewButton;
    protected TextBox nameField;
    protected TextBox emailField;
    protected TextBox phoneField;
    protected Label status;
    protected Grid contactGrid;
    protected Grid formGrid;
    
    /* Data model */
    private List<Contact> contacts;
    private Contact currentContact;
    protected ContactServiceDelegate contactService;
        
    public void init() {
        addButton = new Button("Add new contact");
        addNewButton = new Button("Add new contact");
        updateButton = new Button("Update contact");
        nameField = new TextBox();
        emailField = new TextBox();
        phoneField = new TextBox();
        status = new Label();
        contactGrid = new Grid(2,5);

        buildForm();
        placeWidgets();
    }
    
    private void buildForm() {
        formGrid = new Grid(4,3);
        formGrid.setVisible(false);
        
        formGrid.setWidget(0, 0, new Label("Name"));
        formGrid.setWidget(0, 1, nameField);

        formGrid.setWidget(1, 0, new Label("email"));
        formGrid.setWidget(1, 1, emailField);
        
        formGrid.setWidget(2, 0, new Label("phone"));
        formGrid.setWidget(2, 1, phoneField);
        
        formGrid.setWidget(3, 0, updateButton);
        formGrid.setWidget(3, 1, addButton);
        
    }

    private void placeWidgets() {
        RootPanel.get(CONTACT_LISTING_ROOT_PANEL).add(contactGrid);
        RootPanel.get(CONTACT_FORM_ROOT_PANEL).add(formGrid);
        RootPanel.get(CONTACT_STATUS_ROOT_PANEL).add(status);
        RootPanel.get(CONTACT_TOOL_BAR_ROOT_PANEL).add(addNewButton);
    }

    private void loadForm(Contact contact) {
        this.formGrid.setVisible(true);
        currentContact = contact;
        this.emailField.setText(contact.getEmail());
        this.phoneField.setText(contact.getPhone());
        this.nameField.setText(contact.getName());
    }


    private void copyFieldDateToContact() {
        currentContact.setEmail(emailField.getText());
        currentContact.setName(nameField.getText());
        currentContact.setPhone(phoneField.getText());
    }

    public void gui_eventContactGridClicked(Cell cellClicked) {
         int row = cellClicked.getRowIndex();
         int col = cellClicked.getCellIndex();
        
         Contact contact = this.contacts.get(row);
         this.status.setText("Name was " + contact.getName() + " clicked ");
        
         if (col==EDIT_LINK) {
             this.addNewButton.setVisible(false);
             this.updateButton.setVisible(true);
             this.addButton.setVisible(false);
             this.emailField.setReadOnly(true);
             loadForm(contact);
         } else if (col==REMOVE_LINK) {
             this.contactService.removeContact(contact);
         }
    }


    public void gui_eventAddButtonClicked() {
        addNewButton.setVisible(true);
        formGrid.setVisible(false);
        copyFieldDateToContact();
        this.phoneField.getText();
        this.contactService.addContact(currentContact);
    }

    public void gui_eventUpdateButtonClicked() {
        addNewButton.setVisible(true);
        formGrid.setVisible(false);
        copyFieldDateToContact();
        this.contactService.updateContact(currentContact);
    }

    public void gui_eventAddNewButtonClicked() {
        this.addNewButton.setVisible(false);
        this.updateButton.setVisible(false);
        this.addButton.setVisible(true);
        this.emailField.setReadOnly(false);
        loadForm(new Contact());
    }


    public void service_eventListRetrievedFromService(List<Contact> result) {
        status.setText("Retrieved contact list");
        this.contacts = result;
        this.contactGrid.clear();
        this.contactGrid.resizeRows(this.contacts.size());
        int row = 0;
        
        for (Contact contact : result) {
            this.contactGrid.setWidget(row, 0, new Label(contact.getName()));
            this.contactGrid.setWidget(row, 1, new Label (contact.getPhone()));
            this.contactGrid.setWidget(row, 2, new Label (contact.getEmail()));
            this.contactGrid.setWidget(row, EDIT_LINK, new Hyperlink("Edit", null));
            this.contactGrid.setWidget(row, REMOVE_LINK, new Hyperlink("Remove", null));
            row ++;
        }
    }

    public void service_eventAddContactSuccessful() {
        status.setText("Contact was successfully added");
        this.contactService.listContacts();
    }

    public void service_eventUpdateSuccessful() {
        status.setText("Contact was successfully updated");
        this.contactService.listContacts();
    }
    public void service_eventRemoveContactSuccessful() {
        status.setText("Contact was removed");
        this.contactService.listContacts();
        
    }

    public void service_eventUpdateContactFailed(Throwable caught) {
        status.setText("Update contact failed");
    }

    public void service_eventAddContactFailed(Throwable caught) {
        status.setText("Unable to update contact");
    }

    public void service_eventRemoveContactFailed(Throwable caught) {
        status.setText("Remove contact failed");
    }

    public void service_eventListContactsFailed(Throwable caught) {
        status.setText("Unable to get contact list");
        
    }

}

まとめ

Google App Engine for Java を紹介する、3 回からなる連載の第 2 回では、App Engine for Java の Eclipse プラグイン・ツールを使ってカスタム GWT アプリケーションを作成するプロセスを説明しました。単純な連絡先管理アプリケーションを構築する手順をとおして、以下の方法を学びました。

  • 非同期で機能するリモート・サービスの構築方法
  • ネストされた内部クラスの宣言を避けるように GUI コードを編成する方法
  • GWT を利用して 2 つの主要な使用ケースに対応する機能を実装する方法

連載の第 3 回では、この連絡先管理アプリケーションを改良し、App Engine for Java データ・ストア機能によって Contact オブジェクトを永続化するためのサポートを追加します。次回の記事もお見逃しなく。

参考文献

学ぶために

製品や技術を入手するために

  • Google Plugin for Eclipse: プラグインをダウンロードして使い始めてください。
  • Google App Engine アカウント: 作成したキラー・アプリケーションを Google App Engine インフラストラクチャーを使ってデプロイするには、アカウントが必要です。

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Java technology, Open source, Cloud computing
ArticleID=426802
ArticleTitle=Google App Engine for Java: 第 2 回 キラー・アプリケーションを構築する
publish-date=08112009