IBM®
메인 컨텐츠로 가기
    Korea [국가변경]    이용약관
 
 
   
        제품    서비스 & 솔루션    고객지원 & 다운로드    회원 서비스    
메인 컨텐츠로 가기

한국 developerWorks  >  자바 | 오픈 소스  >

Google App Engine for Java: Part 2: 킬러 애플리케이션 작성하기

App Engine에서 자신의 연락처 관리 애플리케이션 작성하기

developerWorks
문서 옵션
PDF format - Fits A4 and Letter

PDF - Fits A4 and Letter
86KB (19 pages)

Get Adobe® Reader®

JavaScript가 필요한 문서 옵션은 디스플레이되지 않습니다.

영어원문

영어원문


제안 및 의견
피드백

난이도 : 초급

Richard Hightower, CTO, Mammatus Inc.

원문 게재일 : 2009 년 8 월 11 일
번역 게재일 : 2009 년 9 월 15 일

Google App Engine for Java™와 같은 클라우드 플랫폼의 가장 중요한 특징은 매우 많은 비용과 노력을 들이지 않고도 전문가 수준의 킬러 애플리케이션을 상상하고, 개발하고, 전개할 수 있다는 것입니다. Google App Engine for Java를 소개하는 세 편의 기사 중 이 두 번째 기사에서는 기존 예제를 설명했던 Part 1과는 달리 Google App Engine for Java를 사용하여 간단한 연락처 관리 애플리케이션을 작성 및 전개하는 과정을 단계별로 안내합니다.

App Engine for Java를 이용하여 확장 가능한 Java 애플리케이션을 개발하는 방법을 설명하는 이 시리즈의 Part 1에서는 Eclipse 도구와 Java 개발자를 위한 Google의 클라우드 컴퓨팅 플랫폼(또는 PAAS) 인프라에 대해 살펴보았다. Part 1에서는 기존 예제를 사용했기 때문에 App Engine for Java과 Eclipse의 통합에 집중할 수 있었고 다양한 유형의 애플리케이션 즉, GWT(Google Web Toolkit)를 사용하여 작성한 애플리케이션과 서블릿 기반 애플리케이션의 작성 및 전개 과정을 빠르게 익힐 수 있었다. 이 기사에서는 이 시리즈의 Part 3에서 설명할 고급 프로그래밍 주제를 위한 토대를 마련하고 준비한다.

앞으로 작성할 연락처 관리 애플리케이션에서는 사용자가 이름, 이메일 주소, 전화 번호 등의 기본적인 연락처 정보를 저장할 수 있다. 이 애플리케이션을 작성하기 위해 Eclipse GWT 프로젝트 작성 마법사를 사용한다.

CRUD에서 연락처로

지금까지 살펴본 대로 App Engine for Java에서 새 애플리케이션을 작성하기 위해 먼저 Eclipse의 프로젝트 작성 마법사를 실행한다. 이 마법사에서 GWT 프로젝트 시작 마법사를 실행하여 GWT 프로젝트를 작성할 수 있다. (이 시리즈의 Part 1에서 GWT 프로젝트를 App Engine for Java에서 작성하는 방법에 대한 자세한 지침을 볼 수 있다.)

이 기사의 예제에서는 간단한 CRUD 애플리케이션부터 시작한 후 나중에 실제 저장소를 추가한다. 지금은 모의 구현을 가지고 있는 DAO(Data Access Object)를 사용한다(Listing 1 참조).


Listing 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 인터페이스이다. Listing 2의 Contact 클래스가 도메인 오브젝트이다.


Listing 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;
    }
        

}

이 첫 번째 버전의 애플리케이션에서는 연락처를 메모리 내 컬렉션에 저장하는 모의 오브젝트를 사용한다(Listing 3 참조).


Listing 3. Mock 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를 작성할 차례이다. 이 GUI에서는 ContactDAO 인터페이스의 모든 메소드를 사용해야 한다. 가장 먼저 수행할 작업은 Listing 4와 같이 DAO 클래스의 기능을 하나의 서비스로 랩핑하는 것이다. (후속 버전에서는 서버측에 있는 데이터 저장소와 실제로 통신할 것이므로 이러한 기능이 서버에 있어야 한다).


Listing 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에 위임한다. ContactServiceImplContactDAO에 대한 랩퍼로서 ContactDAO 기능을 GWT GUI에 표시하는 역할만 수행한다. ContactServiceImpl은 web.xml 파일에서 URI /contactlist/contacts에 맵핑된다(Listing 5 참조).


Listing 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 프론트 엔드에서 이 서비스에 액세스하는 것을 허용하려면 원격 서비스 인터페이스와 비동기 원격 서비스 인터페이스 둘 다를 정의해야 한다(Listing 6 및 7 참조).


Listing 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);
}


Listing 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 인터페이스를 구현하고 "contacts"의 상대 경로를 지정하는 @RemoteServiceRelativePath를 정의한다. 이 상대 경로는 web.xml 파일에서 서비스에 대해 정의한 경로에 해당하므로 두 경로가 일치해야 한다. ContactServiceAsync에는 콜백 오브젝트가 있으므로 GWT GUI에서 다른 클라이언트 활동을 차단하지 않고 서버의 호출을 인식할 수 있다.

스파게티 코드 없애기

필자는 스파게티 코드를 좋아하지 않기 때문에 되도록이면 스파게티 코드를 작성하지 않으려고 한다. 스파게티 코드의 예로는 익명 내부 클래스를 정의하는 메소드를 가지고 있는 익명 내부 클래스 그룹이 있다. 이러한 내부 클래스는 메소드를 호출하는 콜백을 수행하여 내부 클래스 내에 정의된다. 솔직히 말해서 이러한 코드는 필자가 직접 작성한 코드라고 하더라도 너무 복잡해서 읽고 이해할 수가 없다. 따라서 복잡한 코드를 조금 더 단순하게 만들기 위해 GWT GUI를 다음 세 부분으로 나누고자 한다.

  • ContactListEntryPoint
  • ContactServiceDelegate
  • ContactListGUI

ContactListEntryPoint는 기본 진입점으로 GUI 이벤트 연결을 수행한다. ContactServiceDelegateContactService 기능을 랩핑하고 내부 클래스 콜백 연결을 숨긴다. ContactListGUI는 모든 GUI 구성 요소를 관리하고 GUIService의 이벤트를 처리한다. ContactListGUIContactServiceDelegate를 사용하여 ContactService를 요청한다.

ContactList.gwt.xml 파일(gaej.example.contact 아래에 있는 리소스)은 entry-point 요소를 사용하여 ContactListEntryPoint를 애플리케이션에 대한 기본 진입점으로 지정한다(Listing 8 참조).


Listing 8. ContactList.gwt.xml

<entry-point class='gaej.example.contact.client.ContactListEntryPoint'/>

ContactListEntryPoint 클래스는 GWT(com.google.gwt.core.client.EntryPoint)의 EntryPoint 인터페이스를 구현한다. 즉, 이 클래스는 GUI를 초기화하기 위해 호출된다. ContactListEntryPoint는 많은 작업을 수행하지 않는다. ContactListGUI의 인스턴스와 ContactServiceDelegate의 인스턴스를 작성한 후 두 인스턴스가 협업할 수 있도록 상대 인스턴스에 대한 정보를 제공한다. 그런 다음 ContactListEntryPoint는 GUI 이벤트 연결을 수행한다. Listing 9에서는 ContactListEntryPoint를 보여 준다.


Listing 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();
                
            }});

    }
}

ContactListEntryPointaddButton, updateButton, contactGridaddNewButton에 대한 이벤트를 연결한다. 이러한 연결은 위젯 이벤트에 대한 리스너 인터페이스를 구현한 익명 내부 클래스를 등록하여 수행된다. 이 방법은 Swing의 이벤트 처리 기술과 매우 유사하다. 위젯 이벤트는 GUI(ContactListGUI)에 의해 작성된 위젯에서 발생하며 뒷부분에서 이에 대해 간단히 다룰 것이다. 그리고 GUI 클래스에는 GUI 이벤트에 응답하기 위한 gui_eventXXX 메소드가 있다.

ContactListGUI는 GUI 위젯을 작성하고 이러한 위젯의 이벤트에 응답한다. ContactListGUI는 GUI 이벤트를 사용자가 ContactsService에 대해 수행하려는 동작으로 변환한다. ContactListGUIContactServiceDelegate를 사용하여 ContactService의 메소드를 호출하며 ContactServiceDelegateContactService에 대한 비동기 인터페이스를 작성한 후 이 인터페이스를 사용하여 비동기 Ajax 호출을 만든다. ContactServiceDelegateContactListGUI에 서비스의 이벤트(성공 또는 실패 리턴)를 통지한다. Listing 10에서는 ContactServiceDelegate를 보여 준다.


Listing 10. ContactServiceDelegatepackage 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.        
    }
    
}

ContactServiceDelegateservice_eventXXX로 시작하는 메소드를 통해 ContactListGUI에 서비스 이벤트를 통지한다. 앞에서 언급한 대로 ContactListGUI를 작성하는 목적 중 하나는 중첩된 내부 클래스를 사용하지 않고 비교적 일반적인 GUI 클래스(나중에 쉽게 읽고 작업할 수 있는 클래스)를 작성하는 것이다. ContactListGUI는 186행에 불과하며 상당히 쉽게 파악할 수 있다. ContactListGUI는 9개의 GUI 위젯을 관리하고 ContactServiceDelegate와의 협업을 통해 CRUD 목록을 관리한다(Listing 11 참조).


Listing 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에 사용되는 위젯

Listing 12에서는 ContactListGUI가 위젯과 연락처 양식을 작성한 후 위젯을 양식에 배치하는 방법을 보여 준다.


Listing 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);
    }

ContactListGUI init 메소드는 ContactListEntryPoint.onModuleLoad 메소드에 의해 호출된다. init 메소드는 buildForm 메소드를 호출하여 새 양식 표를 작성한 후 이 표에 연락처 데이터를 편집하기 위한 필드를 채운다. 그런 다음 init 메소드는 placeWidgets 메소드를 호출하여 이 GUI 애플리케이션을 호스팅하는 HTML 페이지에 정의되어 있는 슬롯(Listing 13 참조)에 contactGrid, formGrid, statusaddNewButton 위젯을 배치한다.


Listing 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 메소드가 호출된다. onModuleLoad는 서비스의 listContact 메소드를 비동기적으로 호출하는 ContactServiceDelegatelistContacts 메소드를 호출한다. listContact 메소드가 리턴되면 ContactServiceDelegate에 정의된 익명 내부 클래스가 service_eventListRetrievedFromService라는 서비스 이벤트 처리기 메소드를 호출한다(Listing 14 참조).


Listing 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를 지운 다음 서버에서 리턴한 연락처 목록의 크기에 맞게 행의 수를 변경한다. 그런 다음 연락처 목록을 반복하면서 각 연락처의 이름, 전화 번호 및 이메일 데이터를 각 행의 앞쪽 세 개의 열에 배치한다. 그리고 이 메소드는 사용자가 연락처를 쉽게 제거 및 편집할 수 있도록 각 연락처에 대해 Edit 링크와 Remove 링크를 제공한다.

기존 연락처 편집하기

사용자가 연락처 목록의 Edit 링크를 클릭하면 Listing 15의 gui_eventContactGridClicked가 호출된다.


Listing 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를 읽기 전용으로 설정한다. 그런 다음 gui_eventContactGridClicked에서 loadForm(Listing 15 참조)이 호출된다. 이제 formGrid표시되도록 설정되고 연락처가 편집 중인 것으로 설정되며 연락처 특성이 emailField, phoneFieldnameField 위젯에 복사된다.

사용자가 updateButton을 클릭하면 gui_eventUpdateButtonClicked 이벤트 처리기 메소드가 호출된다(Listing 16 참조). 이 메소드는 addNewButton을 표시되도록 설정하고(따라서 사용자가 새 연락처를 추가할 수 있음) formGrid를 숨긴다. 그런 다음 copyFieldDateToContact를 호출하여 emailField, phoneFieldnameField 위젯의 텍스트를 currentContact의 특성에 다시 복사한다. 그런 다음 ContactServiceDelegate updateContact 메소드를 호출하여 새로 업데이트된 연락처를 서비스에 다시 전달한다.


Listing 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());
    }

이들 두 시나리오를 보면 애플리케이션의 작동 방법과 App Engine for Java에서 제공하는 인프라를 활용하는 방법을 알 수 있다. Listing 17에서는 ContactListGUI의 전체 코드를 보여 준다.


Listing 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를 소개하는 세 편의 기사 중 이 두 번째 기사에서는 App Engine for Java를 위한 Eclipse 플러그인 도구를 사용하여 사용자 정의 GWT 애플리케이션을 작성하는 과정을 살펴보았다. 간단한 연락처 관리 애플리케이션을 작성하는 과정을 통해 설명한 내용은 다음과 같다.

  • 비동기적으로 작동하는 원격 서비스 작성하기
  • 중첩된 내부 클래스 선언이 없도록 GUI 코드 구성하기
  • GWT를 활용하여 두 가지 주요 사용 사례를 위한 기능 구현하기

이 시리즈의 Part 3에서는 연락처 관리 애플리케이션을 구체화한 후 App Engine for Java 데이터 저장소 기능을 사용하여 Contact 오브젝트를 유지하기 위해 필요한 지원을 추가한다.



참고자료

교육

제품 및 기술 얻기

토론


필자소개

Rick Hightower는 클라우드 컴퓨팅, GWT, Java EE, Spring 및 Hibernate 개발과 관련된 교육 서비스를 전문으로 제공하는 회사인 Mammatus Inc.의 CIO이다. 유명한 Java Tools for Extreme Programming의 공동 저자이며 TheServerSide.com에서 여러 해 동안 다운로드 수가 가장 높았던 Struts Live 초판의 저자이다. IBM developerWorks에 많은 기사와 튜토리얼을 기고하고 있는 그는 Java Developer's Journal의 편집위원으로 활동하고 있으며 DZone에도 Java 및 Groovy와 관련된 많은 글을 기고하고 있다.




기사에 대한 평가


보다 나은 서비스를 제공하기 위함이오니 잠시 짬을 내어 이 양식을 제출하여 주십시오.



 


 


 


이 문서 북마킹 하기

mar.gar.in mar.gar.in naver naver eolin eolin del.icio.us del.icio.us





위로


developerWorks 콘텐트를 다른 사이트에 전재하기:
developerWorks 콘텐트에 대한 저작권은 IBM에 있습니다. IBM의 서면 허가나 원본 저자의 허락이 없이는 전재를 금합니다. 저희 콘텐트를 전재하시려면 IBM developerWorks 담당자 에게 문의하십시오.
    IBM 소개 개인정보 보호정책 문의