Платформа Google App Engine для Java-приложений: Часть 2. Создание шедевра

Создание вашего собственного менеджера контактных данных для платформы Google App Engine

Основная прелесть любой платформы облачных вычислений, такой как Google App Engine для Java, заключается в том, что вы можете проектировать, реализовывать и развертывать масштабируемые системы профессионального уровня без умопомрачительных затрат. В отличие от предыдущей части, в которой приводились готовые примеры, в этой статье Ричард Хайтауэр пошагово описывает процесс создания простого приложения-менеджера контактов и его развертывания на платформе Google App Engine.

Ричард Хайтауэр (Richard Hightower), разработчик, ArcMind Inc.

Фотография автораРичард Хайтауэр (Rick Hightower) работает начальником отдела разработчиков в ArcMind Inc - компания, специализирующаяся на JSF, Spring и Hibernate. Он является соавтором известной книги Инструменты Java для экстремального программирования, в которой рассказывается, как применять экстремальное программирование в J2EE, и соавтором Struts для профессионалов.



29.06.2011

В первой статье серии, посвященной введению в создание масштабируемых Java-приложений для App Engine, вы познакомились с модулем для Eclipse и инфраструктурой платформы облачных вычислений Google (PAAS) для Java-разработчиков. Примеры в той статье были подготовлены заранее, чтобы можно было уделить основное внимание интеграции App Engine для Java с Eclipse и попрактиковаться в сборке и развертывании приложений различных типов, в том числе созданных на базе сервлетов и инструментария GWT (Google Web Toolkit). Эта статья развивает тему предыдущей части, а также готовит вас к более сложному процессу разработки, который будет продемонстрирован в заключительной статье серии.

Менеджер контактов представляет собой приложение, позволяющее пользователям хранить основные контактные данные, такие как имя, адрес электронной почты и номер телефона. Для создания этого приложения мы воспользуемся мастером Eclipse для инициализации проектов на базе GWT.

Менеджер контактов как приложение CRUD

Как вы уже знаете, разработка нового приложения для App Engine начинается с запуска мастера Eclipse для создания нового проекта. В нашем случае следует выбрать мастер инициализации приложения на основе GWT. Подробную информацию об использовании этого мастера можно найти в предыдущей статье.

Мы начнем с создания простого CRUD-приложения, к которому позже будет добавлено реальное хранилище данных. Пока же мы будем использовать объект доступа к данным (data access object – DAO) с фиктивной реализацией, которая приведена в листинге 1.

Листинг 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. Класс объектов предметной области (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. Фиктивная реализация интерфейса доступа к данным
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);
    }

}

Создание удаленных сервисов

Нашей следующей задачей является создание графического интерфейса на базе GWT, позволяющего вызывать все методы интерфейса ContactDAO. Вначале необходимо создать сервис-обертку вокруг класса DAO, как показано в листинге 4. Как вы помните, в будущем DAO будет обращаться к серверному хранилищу данных, поэтому он должен выполняться на стороне сервера.

Листинг 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. Этот класс связан с URI /contactlist/contacts в файле web.xml, как показано в листинге 5.

Листинг 5. Файл web.xml, содержащий отображение сервиса ContactService
  <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>

Далее необходимо определить интерфейсы удаленного сервиса, а также его асинхронной версии, чтобы к нему можно было обращаться через графический интерфейс. Эти интерфейсы приведены в листингах 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, в которой указывается относительный путь "contacts". Этот путь должен соответствовать URI, связанному с сервисом в файле web.xml. Методы интерфейса ContactServiceAsync принимают на вход объекты обратного вызова, благодаря которым GUI получает уведомления от сервера без блокирования выполнения клиентских потоков.

Распутывание спагетти-кода

Меня никак нельзя назвать любителем спагетти-кода (spaghetti code), и я стараюсь избегать его везде, где только возможно. Примером такого кода может служить набор анонимных внутренних классов, методы которых содержат другие анонимные классы. При этом эти классы принимают на вход объекты обратного вызова, которые вызывают методы, определенные здесь же, т.е. во внутренних классах. О боже! Честно говоря, я не могу разобраться в таком коде, даже когда сам являюсь его автором. Поэтому я предлагаю распутать код графического интерфейса на базе GUI, разбив его на три следующих компонента:

  • ContactListEntryPoint
  • ContactServiceDelegate
  • ContactListGUI

Класс ContactListEntryPoint является отправной точкой интерфейса, отвечая за связывания событий с обработчиками. ContactServiceDelegate представляет собой обертку вокруг ContactService, скрывая такие детали, как задание обработчиков, являющихся экземплярами внутренних классов. ContactListGUI управляет всеми компонентами графического интерфейса и обрабатывает события, полученные от GUI и удаленного сервиса. Этот класс использует ContactServiceDelegate для выполнения запросов к ContactService.

В файле ContactList.gwt.xml, являющемся ресурсом в пакете gaej.example.contact, указывается, что функции точки входа в приложение выполняет класс ContactListEntryPoint. Для этого служит элемент entry-point, как показано на примере в листинге 8.

Листинг 8. Файл ContactList.gwt.xml
<entry-point class='gaej.example.contact.client.ContactListEntryPoint'/>

Класс ContactListEntryPoint  реализует стандартный GWT-интерфейс com.google.gwt.core.client.EntryPoint, что говорит о том, что он должен вызываться для инициализации графического интерфейса. При этом ContactListEntryPoint выполняет не так много действий. Он создает экземпляры ContactListGUI и ContactServiceDelegate, а затем "знакомит" их друг с другом, чтобы они могли взаимодействовать самостоятельно. После этого он связывает события GUI с соответствующими обработчиками, как показано в листинге 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;

/**
 * Классы, играющие роль точек входа, должны определять метод  onModuleLoad().
 */
public class ContactListEntryPoint implements EntryPoint {
    private ContactListGUI gui;
    private ContactServiceDelegate delegate;
    
    /**
     * Метод, представляющий точку входа в приложение
     */
    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 устанавливает обработчики событий для компонентов addButton, updateButton, contactGrid и addNewButton. В качестве обработчиков он регистрирует экземпляры анонимных внутренних классов, которые реализуют интерфейс слушателя событий, поступающих от виджетов (очень похожий подход используется в Swing). К таким событиям относятся те, которые генерируются виджетами, созданными GUI-классами, в частности ContactListGUI, который будет рассмотрен чуть позже. Обратите внимание, что этот класс содержит методы gui_eventXXX, реагирующие на события GUI.

Класс ContactListGUI  создает виджеты и откликается на сгенерированные ими события. Кроме того, он преобразует события GUI в действия, которые должны быть выполнены сервисом ContactsService, вызывая его методы при помощи посредника ContactServiceDelegate. Данный класс-делегат создает асинхронный интерфейс к ContactService  и использует его для выполнения асинхронных вызовов через Ajax. Кроме того, он уведомляет ContactListGUI о событиях сервиса, при помощи которых тот сигнализирует об успешном или неудачном завершении ранее вызванного метода. Код класса ContactServiceDelegate приведен в листинге 10.

Листинг 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);
                        
                    }
        }//окончание внутреннего класса
        );//окончание метода listContacts
    }
    
    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();
            }
        }//окончание внутреннего класса
        );//окончание метода addContact        
    }

    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();
            }
        }//окончание внутреннего класса
        );//окончание метода updateContact        
    }

    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();
            }
        }//окончание внутреннего класса
        );//окончание метода removeContact
    }
    
}

Обратите внимание на то, как ContactServiceDelegate уведомляет ContactListGUI о событиях, возбужденных сервисом, при помощи методов с префиксом service_eventXXX. Как упоминалось выше, одной из целей создания ContactListGUI было желание избавиться от вложенных внутренних классов путем их замены на плоский GUI-класс, код которого был бы понятен и легок при дальнейшем использовании. Весь код ContactListGUI занимает всего 186 строк и не представляет трудностей при чтении. Этот класс управляет девятью виджетами, а также взаимодействует с 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 {
    /* константы */
    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;

    /* графические компоненты (виджеты) */
    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;
    
    /* объекты модели данных */
    private List<Contact> contacts;
    private Contact currentContact;
    protected ContactServiceDelegate contactService;

Также следует отметить, что ContactListGUI хранит контактную запись, загруженную в форму ввода (переменная currentContact), а также текущий список контактов (contacts). На рисунке 1 указаны имена компонентов графического интерфейса приложения.

Рисунок 1. Виджеты, использующиеся в менеджере контактов
Виджеты, использующиеся в менеджере контактов

В листинге 12 демонстрируется то, как класс ContactListGUI создает форму ввода контактных записей и виджеты, которые затем размещаются на ранее созданной форме.

Листинг 12. Пример создания и размещения виджетов классом ContactListGUI
public class ContactListGUI {
    /* константы */
    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);
    }

Метод init класса ContactListGUI вызывается из метода ContactListEntryPoint.onModuleLoad. В свою очередь он вызывает метод buildForm для создания новой разметки формы и ее заполнения полями ввода и редактирования контактных данных. Затем метод init вызывает метод placeWidgets, который помещает виджеты contactGrid, formGrid, status и addNewButton в области разметки, определенные в базовой 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="contactListing", определенным на HTML-странице. Это позволяет Web-дизайнеру более гибко управлять размещением графических компонентов пользовательского интерфейса.

Закончив создание основной части приложения, мы перейдем к рассмотрению типичных сценариев его использования.

Вывод списка контактов при загрузке страницы

В момент загрузки главная страница менеджера контактов вызывает метод ContactListEntryPoint.onModuleLoad, который обращается к ContactServiceDelegate.listContacts, а тот, в свою очередь, делает асинхронный вызов метода 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, в котором отображается контактная информация. После этого он устанавливает число строк в области отображения в соответствии с числом записей, полученных от сервера. Наконец, он перебирает все контакты в списке, помещая имя, телефон и адрес электронной почты каждого из них в первые три колонки очередной строки. Кроме того, этот метод добавляет рядом с каждым контактом ссылки Edit (редактировать) и Remove (удалить), чтобы пользователи могли изменять и удалять контактные данные.

Редактирование существующего контакта

При нажатии пользователем на ссылку Edit вызывается метод gui_eventContactGridClicked, код которого приведен в листинге 15.

Листинг 15. Метод-обработчик gui_eventContactGridClicked в классе ContactListGUI
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) нажал пользователь. Для этой цели он использует индекс колонки, в которой находилась ссылка. Затем метод скрывает кнопки addNewButton и addButton, а кнопку updateButton, наоборот, делает видимой для пользователя. Кнопка updateButton отображается в компоненте formGrid, позволяя пользователю отправлять отредактированные данные обратно сервису ContactService. Кроме того, метод делает поле emailField доступным только для чтения, чтобы пользователь не мог редактировать адрес электронной почты. После этого метод gui_eventContactGridClicked вызывает loadForm (см. листинг 15), который делает компонент formGrid видимым, загружает редактируемый контакт, а затем копирует атрибуты контакта в виджеты emailField, phoneField и nameField.

Метод gui_eventUpdateButtonClicked, показанный в листинге 16, вызывается при нажатии пользователем на кнопку updateButton. Он делает кнопку addNewButton видимой, чтобы пользователь мог добавлять новые контакты, скрывает компонент formGrid, а затем вызывает метод copyFieldDateToContact, который копирует строковые значения виджетов emailField, phoneField и nameField в свойства объекта currentContact. После этого он вызывает метод ContactServiceDelegate.updateContact, который передает только что отредактированную контактную запись сервису.

Листинг 16. Метод-обработчик gui_eventUpdateButtonClicked в классе ContactListGUI
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 для Java. Полностью код класса ContactListGUI приведен в листинге 17.

Листинг 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 {
    /* константы */
    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;

    /* графические компоненты */
    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;
    
    /* объекты модели данных */
    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 для Java, был рассмотрен процесс создания GWT-приложения для App Engine при помощи встраиваемого модуля для Eclipse. На примере приложения для управления контактами вы узнали о том, как:

  • создавать удаленные сервисы с асинхронным доступом;
  • проектировать код GUI, избегая вложенных внутренних классов;
  • использовать GWT для реализации двух ключевых функций приложения.

Оставайтесь с нами, и в третьей статье серии мы модернизируем менеджер контактов, добавив в него возможность сохранения контактов в базе данных при помощи возможностей App Engine для хранения Java-объектов.

Ресурсы

Научиться

Получить продукты и технологии

Обсудить

  • Подпишитесь на сообщения дискуссионной группы App Engine для Java, чтобы иметь возможность задавать вопросы и помогать другим разработчикам в процессе изучения платформы Google App Engine. (EN)

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Мобильные приложения, Open source
ArticleID=696381
ArticleTitle=Платформа Google App Engine для Java-приложений: Часть 2. Создание шедевра
publish-date=06292011