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.

