Datenbankaustausch++

Google Web Toolkit: Integration des GWT Editor Frameworks in MVP-Applikationen
Kommentare

In Webapplikationen werden immer komplexere Sachverhalte abgebildet, die die Verwendung von vielen Formularelementen wie Textfeldern, Comboboxen, Radiobuttons und anderen Eingabeelementen notwendig machen. Die Abbildung dieser Daten beim Speichern und Laden kann ziemlich aufwändig sein. Doch das GWT Editor Framework schafft Abhilfe.

Beim Speichern von Daten müssen die Inhalte dieser Felder ausgelesen und in eine Struktur gegossen werden, die dem dahinterliegenden Modell entspricht. Beim Laden von Daten müssen die Datenobjekte aufgeteilt und in die entsprechenden Felder hinein gesetzt werden. Das GWT Editor Framework ermöglicht es, die in einer Gruppe von Objekten gespeicherten Daten auf eine Gruppe von Editoren abzubilden. Damit ist das manuelle Setzen von Daten in UI-Felder beim Laden von Objekten bzw. das Auslesen von Daten aus dem UI beim Speichern der Objekte nicht mehr nötig. Einzelne Aufrufe der Form (setX(fieldA.getText()) werden damit überflüssig. Der Datenaustausch zwischen Datenobjekten und UI kann unter anderem über die GWT RequestFactory oder RPC erfolgen.

Editoren

Editoren entsprechen einzelnen UI-Elementen. Diese Elemente sind entweder Objekte des GWT-UI-API oder maßgeschneiderte und spezifische Elemente. Diese spezifischen Elemente sind gesamte UI-Ansichten oder Teilobjekte, die als Subeditoren bezeichnet werden. Die Subeditoren werden benötigt, wenn Elemente des GWT-API nicht als Editor zur Verfügung gestellt werden. Des Weiteren werden Gruppen von Elementen, die wiederum Subelemente enthalten können, zu Subeditoren zusammengefasst. Eine Verschachtelung von Editorobjekten ist also möglich. (vgl. die Beschreibung im GWT Developer̕’s Guide: „The GWT Editor framework allows data stored in an object graph to be mapped onto a graph of Editors.“). Damit ein Element von GWT als editierbar wahrgenommen wird, muss eine UI-Ansicht bzw. ein editierbares Element das GWT-Interface „Editor“ oder ein Subinterface von diesem implementieren.
In unseren Applikationen besteht eine UI-Ansicht aus einer Java-Klasse und einer XML-Datei. Diese Dateien enthalten UI-Elemente des GWT-API, wie zum Beispiel TextBox, TextArea oder CheckBox und von uns erweiterte Elemente, die in der benötigten Ausprägung des API nicht zur Verfügung gestellt werden. Ein von uns benötigtes Element, nämlich Comboboxen, werden vom GWT-API nicht in einer editierbaren Form unterstützt. Deshalb wurde ein Subeditor erstellt, der die benötigte Funktionalität zur Verfügung stellt.

Datenobjekte

Die Datenobjekte stellen Bean-like objects, wie EJB Beans oder Spring-Entitäten, dar. Sie verfügen über stark typisierte Getter- und optionale Setter-Methoden, die den Zugriff auf die Daten ermöglichen. Wenn es zum Beispiel in der Spring-Entität Customer ein Feld String name gibt, so ist für dieses Feld zumindest der Getter String getName() zu implementieren. Sofern das Feld durch den Editor verändert werden soll, ist auch ein Setter void setName(String name) hinzuzufügen. Die Datenobjekte können wie die Editoren verschachtelt sein, d. h. die Entität Customer kann ein Objekt Address enthalten, das auf die dazugehörige Entität verweist.
In unseren Applikationen werden Datenobjekte durch von Roo generierte JPA-Entitäten repräsentiert. Die Getter und Setter sind in den zu einzelnen Entitäten gehörenden Proxies bzw. AspectJ-Aspekten umgesetzt. Die Proxies werden vom Client verwendet, die Aspekte serverseitig. Die Daten werden über die GWT RequestFactory zwischen Client und Server übertragen. Die RequestFactory ermöglicht objektorientierte Interaktionen zwischen Client und Server. Über diese Factory können Methoden der Entität aufgerufen und Rückgabewerte dieser Methoden empfangen werden. Sofern die Rückgabewerte Datenobjekte sind, werden clientseitig Proxies zurückgegeben. Die für den Client verfügbaren Methoden der Entitäten werden in dazugehörigen Request-Interfaces deklariert. Auf die zur Verfügung stehenden Requests kann über die RequestFactory (die Factory für clientseitige Requests) zugegriffen werden.

EditorDriver

EditorDriver stellen die Verbindung zwischen Editoren und Datenobjekten her. Zudem stellt ein EditorDriver einen „top-level“-Controller dar. Dieser führt einerseits die Initialisierung des UI mit Daten, andererseits das Schreiben der Daten von dem UI in die Datenobjekte durch. Das GWT-API stellt verschiedene EditorDriver wie den RequestFactoryEditorDriver oder den SimpleBeanEditorDriver zur Verfügung. Diese werden in Abhängigkeit der gewählten Interaktionsform zwischen UI und Datenobjekten einer Applikation verwendet.
Innerhalb unserer Applikation wird eine spezielle Ausprägung des Drivers, der vom GWT-API zur Verfügung gestellte RequestFactoryEditorDriver, verwendet, da der Datenaustausch über die GWT RequestFactory erfolgt.

Bewertung des Frameworks

Das GWT Editor Framework ist mit verschiedenen Datenobjekten kompatibel, wie zum Beispiel Spring-Entitäten oder Plain Old Java Objects (POJOs). Zudem können, wie bereits erwähnt, unterschiedliche Datenübertragungsmechanismen wie die RequestFactory oder RPC eingesetzt werden. Ein weiterer Vorteil ist die Minimierung von Glue-Code, der nur die Entitäten mit dem UI verbindet und keine Funktionalität enthält. Zum Bespiel werden keine Getter- und Setter-Methoden mehr für die einzelnen UI-Elemente benötigt. Des Weiteren unterstützt das Framework clientseitig die Java-Bean-Validierung (JSR 303). Als negativer Aspekt ist an dieser Stelle anzumerken, dass abgesehen vom Google Devloper’s Guide und Diskussionen auf Stack Overflow nur wenig Dokumentation zum GWT Editor Framework verfügbar ist.

Aufmacherbild: TInformation concept: CMYK Data Exchange on linen fabric texture background, 3d render von Shutterstock / Urheberrecht: Maksim Kabakou

[ header = Seite 2: Umsetzung ]

Umsetzung

Die UI-Elemente der Editoren werden in unserer Implementierung auf die Namen der Getter- bzw. Setter-Methoden im Proxy abgebildet, d. h. ein Mapping wird erstellt. Die Auswirkung auf den Quellcode ist daher, dass die Namen der UI-Elemente den Namen der Getter- und Setter-Methoden entsprechen müssen, sofern keine Path-Annotation angewandt wird. Für Abweichungen oder Verschachtelungen innerhalb der Proxies können die Namen über die Path-Annotation für einzelne UI-Elemente überladen werden. Das ist beispielsweise notwendig, wenn mit der abgebildeten Entität verknüpfte Entitäten oder nur einzelne Attribute von verknüpften Entitäten in das UI eingebunden werden sollen. Ein Beispiel für die zwei beschriebenen Formen des Mappings, über Namen und über Path-Annotations zeigt Listing 1.

public interface CustomerProxy extends EntityProxy {
  abstract String getName();
  abstract AddressProxy getAddress();
}
public interface AddressProxy extends EntityProxy {
  abstract String getStreet();
}
public class CustomerUI implements Editor<CustomerProxy>{
  @UiField
  TextBox name;
  @Path("address.street")
  @UiField
  TextBox street;
}

Unsere Applikationen werden mit dem Model View Presenter Pattern (MVP) umgesetzt. Der Vorteil von MVP ist, dass die View völlig vom Model entkoppelt ist, indem ein Presenter dazwischen geschaltet wird. Dieses Pattern dient der Trennung von der darstellenden Schicht der Applikation (View) von der Businesslogik (Model). Für große Applikationen, die mit GWT umgesetzt werden, empfiehlt Google den Einsatz von MVP. Aufgrund der Skalierbarkeit und Modularität von MVP setzen wir das Pattern gemäß Googles Empfehlung in unseren GWT-Applikationen ein. Auf das MVP Pattern wird in einem der kommenden Artikel näher eingegangen.
Eines unserer Ziele war es, Daten mithilfe einer definierten Sequenz von Bildschirmmasken zu erfassen. Dabei sollen die einzelnen Masken aufeinander aufbauen können. Um auf die nächste Bildschirmmaske zu gelangen, muss der WEITER-Button geklickt werden (Abb. 1). Und um auf eine bereits verlassene Maske zu gelangen, können der ZURÜCK-Button oder die Browsernavigationsbuttons verwendet werden. Die Felder in den Bildschirmmasken werden, falls bereits Daten vorhanden sind, ausgefüllt und können verändert werden. Bei einem Klick auf WEITER werden die Daten in die entsprechende Entität gespeichert.

Abb. 1: Mock-up der Applikation

Um die Editorfunktionalität in unsere MVP-Implementierung zu integrieren und die Navigation zwischen den Bildschirmmasken zu ermöglichen, wurde ein Subinterface des Editors ExtEditor (Listing 2) und der ExtEditorPresenter (Listing 3) erstellt.

import com.google.gwt.editor.client.Editor;
public interface ExtEditor<P> extends Editor<P> {
  public void exit();
  public void setEnabled(boolean wait);
}
public class ExtEditorPresenter<P, V extends ExtEditor<? super P>> {
  private RequestFactoryEditorDriver<P, V> editorDriver;
  private V editor;

  public void init(RequestFactoryEditorDriver<P, V> editorDriver, V editor){
    this.editor = editor;
    this.editorDriver = editorDriver;
  }

  private void clearEditorDriver() {
    editorDriver = null;
  }

  public void save() {
    if(editorDriver != null) {
      try {
        RequestContext request = editorDriver.flush();
        if(editorDriver.hasErrors()) {
          log.info("Editor has Errors");
        }
        setWaiting(true);
        request.fire(new BaseReceiver<Void>() {
          @Override
          public void beforeOnFailure(ServerFailure error) {
            if (editorDriver != null) {
              setWaiting(false);
            }
          }
          @Override
          public void onSuccess(Void ignore) {
            if (editorDriver != null) {
              clearEditorDriver();
              setWaiting(false);
              editor.exit();
            }
          }
        });
      } catch(java.lang.IllegalStateException e) {
        log.info("Auto Bean frozen error? "+e.getStackTrace());
      }
    }
  }
  private void setWaiting(boolean wait) {
    editor.setEnabled(!wait);
  }
}

Das Interface ExtEditor wird wie das Editorinterface von der UI-Klasse (Listing 4) implementiert. Das Interface erweitert das Editorinterface um eine exit– und eine setEnabled-Methode. Die UI-Implementierung der exit-Methode wird nach dem Speichern der Daten aufgerufen und löst die Navigation auf die nächste Maske aus. Um ein mehrfaches Auslösen des Vorgangs zu vermeiden, werden die Buttons während des Speicherns der Daten mit der setEnabled-Methode deaktiviert. Die Buttons werden wieder aktiviert, wenn der Speichervorgang nicht abgeschlossen werden kann. Ein Grund dafür ist beispielsweise, dass Daten nicht korrekt eingegeben wurden (Validierung).
In der UI-Klasse werden darüber hinaus der EditorDriver und der EditorPresenter erzeugt und initialisiert. Des Weiteren gibt es in der UI-Klasse einen ClickHandler für den WEITER-Button. Dieser Handler löst nach einem Klick auf den WEITER-Button den Aufruf der Speicherfunktionalität des ExtEditorPresenters aus.

public class CustomerUI implements ExtEditor<CustomerProxy>, ClickHandler{
  // [..]
  private ExtEditorPresenter<CustomerProxy, CustomerUI> editorPresenter;

  public RequestFactoryEditorDriver<CustomerProxy,CustomerUI> createEditorDriver() {
    editorPresenter = new ExtEditorPresenter<CustomerProxy, CustomerUI> ();
    Driver driver = GWT.create(Driver.class);
    driver.initialize(this);
    editorPresenter.init(driver,this);
    return driver;
  }

  @Override
  public void setEnabled(boolean enabled) {
    button.setEnabled(enabled);
  }
  @Override
  public void exit() {
    presenter.customerNextClicked();
  }
}

Das Speichern wird in der Methode save des ExtEditorPresenters implementiert, die über das UI aufgerufen wird. Dort werden als Erstes die UI-Buttons per setEnabled-Methode deaktiviert, um ein mehrfaches Speichern zu verhindern. Anschließend wird über den EditorDriver das eigentliche Speichern der Daten in den Entitäten durch das fire des flush Requests ausgelöst.
Bei der Navigation zu einer Bildschirmmaske wird die entsprechende Activity aufgerufen. Eine Activity ist verantwortlich für den Aufbau einer Bildschirmmaske. Über die Activity-Klasse werden alle benötigten Daten aus den Entitäten geholt und in das UI gesetzt. In der Activity-Implementierung (Listing 5) wird der Proxy der zu verändernden Entität geladen. Die Methode zur Erzeugung des EditorDrivers wird in der UI-Klasse aufgerufen. Dem EditorDriver werden der Proxy und der Request, der beim Speichern der Daten aufgerufen werden soll, zugewiesen. Die Daten werden, sofern diese bereits vorhanden sind, in die UI-Felder geschrieben.

public class CustomerActivity extends AbstractActivity {
  RequestFactoryEditorDriver<CustomerProxy, CustomerUI> editorDriver;
  @Override
  public void start(final AcceptsOneWidget panel, EventBus eventBus) {
    requestFactory.orderRequest().findOrder(place.getOrderId())
    .fire(new Receiver<OrderProxy>() {
      @Override
      public void onSuccess(CustomerProxy response) {
        orderProxy = response;
        customerProxy = response.getCustomer();
        //create the save request.
        MyOrderRequest r = requestFactory.orderRequest();
        r.setCustomerObjectAndVerify(customerProxy).using(orderProxy);
        editorDriver = view.createEditorDriver();
        editorDriver.edit(contractDataUOWProxy, r);
        editorDriver.flush();
      }
    }
  }
}

Fazit

Für die Anforderungen unserer Software genügen die vorhandenen Elemente des GWT-API oftmals nicht. In diesen Fällen wurde die benötigte Funktionalität mit Subeditoren implementiert. Trotz der mangelnden Dokumentation kann das GWT Editor Framework als gute Möglichkeit angesehen werden, Daten zwischen der View und dem Model auszutauschen. Der Implementierungsaufwand von Methoden, die keine andere Funktion besitzen, als Daten in UI-Feldern zu schreiben oder von UI-Feldern zu lesen, entfällt damit vollständig.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -