Datenanbindung über Konnektoren

Konnektoren stellen die Verbindung zwischen einer Daten-Instanz der Anwendung und einer Formular-Instanz beim Laden und Speichern des Formulars her. Um Daten beim Laden des Formulars aus der Anwendung zu übernehmen, muss das Interface ReadConnector implementieren werden - zum Speichern ein SubmissionConnector. Beide Interfaces werden in DataConnector zusammengefasst.

Was eine "Daten-Instanz" ist, hängt von der unterliegenden Anwendung ab und muss vom jeweiligen Connector ausgelesen bzw. gefüllt werden. Das kann z.B. eine Bean mit set- und get-Methoden sein oder auch direkt eine Datenbank-Tabelle.

Verwendung

Um einen Connector zum Laden der Formular-Daten zu verwenden, muss man vor dem Rendern des Formulars formContext.setConnector(...) aufrufen und dort die Connector-Instanz übergeben. Damit werden sofort die Daten über den Connector in den FormContext geladen.

Es gibt aber auch eine setConnector() Methode, die nur ein Object als Parametertyp besitzt. Auf diese Weise wird ein Daten-Objekt übergeben, dass anhand einer konfigurierten Connector-Klasse ausgewertet wird. Dazu erzeugt die *FormEngine eine Instanz der Connector-Klasse, der das Daten-Objekt als Konstruktor-Parameter übergeben wird. Abhängig von der konfigurierten Connector-Implementierung kann das beispielsweise eine Bean sein oder auch die Id eines Datensatzes in einer Datenbanktabelle.

Um die Formulardaten aus einer Klasse MyData zu laden und in diese zu speichern, kann man z.B. einen Konnektor MyDataConnector verwendet. Dieser wird folgendermaßen in der Formulardefinition angegeben:

<connector class="my.example.MyDataConnector" data-class="MyData" />

Die Angabe der data-class ist optional, beschleunigt aber die Suche nach einem passenden Konstruktur in der Connector-Klasse. Zusätzlich gibt es die optionalen Attribute read und write, die auf false gesetzt werden können, wenn der Connector in einem Formular nicht zum Laden oder Speichern verwendet werden soll. Beim Laden des Formulars in der JSP kann dann eine Instanz der Klasse MyData an den FormContext übergeben.

DefinitionFactory factory = DefinitionFactory.getInstance();
FormDefinition formDefinition = factory.loadDefinition("/path/to/my/form.xml");
FormContext form = FormContext.createFormContext(request, formDefinition);

MyData myData = ...; // eigenes Daten-Object holen

form.setConnector(myData);

In setConnector wird dann anhand der in der Formular-Definition angegebenen Connector-Klasse per Reflections eine Connector-Instanz erzeugt. Dabei wird ein Konstruktor gesucht, der einen Parameter vom Typ des übergebenen Daten-Objekts besitzt. Direkt in dieser Methode wird dann auch der Connector abgefragt, um die Formular-Komponenten zu befüllen. Die dabei erzeugte Connector-Instanz wird dann auch beim Submit des Formulars verwendet, um die eingegebenen Daten zurück in das Bean zu schreiben.

Ohne die Angabe der Connector-Konfiguration in der Formulardefinition kann die Connector-Instanz auch in der JSP selbst erzeugt und direkt an das Formular übergeben werden. Somit lassen sich beliebige Konnektoren mit derselben Formulardefinition verwenden, falls die Daten aus unterschiedlichen Quellen stammen können.

DefinitionFactory factory = DefinitionFactory.getInstance();
FormDefinition formDefinition = factory.loadDefinition("/path/to/my/form.xml");
FormContext form = FormContext.createFormContext(request, formDefinition);

MyData myData = ...; // eigenes Daten-Object holen
MyDataConnector dataConnector = new MyDataConnector(myData);

form.setConnector(dataConnector);

Connector-Implementierung

Ein DataConnector muss die Methoden getValue bzw. setValue bereitstellen. Über getValue werden von der FormEngine die Daten am Connector abgefragt, die in die Formular-Instanz übernommen werden sollen. Dabei werden anhand der Formulardefinition nacheinander alle Komponentennamen abgefragt. Da ein Formular auch hierarchische Strukturen abbilden kann, wird dafür die Klasse HierarchicName verwendet.

Eine einfache Implementierung eines Connectors für das obige MyData-Beispiel, der die Daten einer Bean ausliest, kann dann so aussehen.

// Muss direkt DataConnector implementieren,
// um mit setConnector verwendet werden zu können
public class MyDataConnector implements DataConnector {

  private MyData bean;

  public MyDataConnector(MyData data) {
    bean = data;
  }

  public Object getValue(HierarchicName cName) {
    String name = cName.toString();

    if ("/fieldA".equals(name)) {
      return bean.getFieldA();
    }
    // else if () ...
  }

  public void setValue(HierarchicName cName, Object value) {
    String name = cName.toString();

    if ("/fieldA".equals(name)) {
      bean.setFieldA(value);
    }
    // else if () ...
  }

  public Object submitValues() {
    // Code zur Persistierung der Daten
  }

  ...
}

Darüber hinaus müssen noch weitere Methoden implementiert werden, beispielsweise um bei Repeats im Formular die aktuelle Anzahl Zeilen abzufragen bzw. zu übergeben. Eine ausführlichere Interface-Beschreibung ist in den javadocs der betreffenden Klassen enthalten.

Standard-Implementierungen

Es gibt einige Connector-Implementierungen, die bereits für Standardfälle verwendet werden können oder als Basis für eigene DataConnector-Klassen dienen können.

Der AbstractAttributeConnector ist als Basis für Konnektoren gedacht, welche die Daten aus Attribut-Containern ermitteln. Dabei besitzen die Attribute Namen, die den Namen der Formularfelder entsprechen. Feldnamen in Gruppen oder Repeats werden dabei mit '/' zum vollständigen Attributnamen zusammengefügt (also z.B. address/street). Beispielhafte Implementierungen sind der de.imatics.forms.connector.SessionConnector und der
de.imatics.forms.connector.RequestParameterConnector.

Für Beans mit entsprechenden set-Methoden, gibt es den ReflectionConnector. Dabei müssen die setter und getter so heißen, wie die Komponenten in der Formulardefinition. Der Anfangsbuchstabe des Komponentennamens und nach jedem '-'-Zeichen wird dabei in Großbuchstaben umgewandelt. z.B. muss eine Bean für die Komponente "full-name" die Methoden getFullName und setFullName besitzen. Die Klassen der Rückgabewerte und der Setter-Parameter müssen außerdem dem Typ der Komponenten entsprechen.

Bei Gruppen und Repeats müssen die Setter komplexe Daten-Objekte liefern. Für eine Gruppe "address", welche die Adressdaten einer Person zusammenfasst, muss es also eine Methode getAddress() geben, die wieder ein Address-Bean zurückgibt. Dieses sollte dann z.B. die Methoden getStreet() und getZip() besitzen. Für Repeats werden Listen von Beans erwartet, wobei über generische Typ-Parameter der Typ der enthaltenen Beans angegeben werden muss. Also bei einer Liste von Adressen z.B. folgende Methode:

public List<AddressBean> getAddresses() {
  ...
}

Für ein leeres Repeat gibt man einfach eine leere Liste zurück.
Über setAddresses(List<AddressBean> addresses) wird beim Submit die Liste der gefüllten Beans übergeben.

Der ReflectionConnector mappt also die Struktur des Formulars auf eine entsprechende Bean-Struktur. Alternativ können die Beans auch die vereinfachten Methoden setValue(String name, Object value) bzw. Object getValue(String name) besitzen.