HBCI4Java kommuniziert mit der Anwendung bzw. dem Anwender über Callbacks.

Eine Beschreibung der Callback-Schnittstelle befindet sich in der Interface-Beschreibung zu `HBCICallback`. Hier soll kurz erläutert werden, wie ein generischer Callback-Handler implementiert werden kann. Unter einem generischen Callback-Handler stelle man sich ein `HBCICallback`-Objekt vor, welches auf alle möglichen auftretenden Callbacks sinnvoll reagiert, ohne jeden einzelnen Callback-Grund separat zu behandeln. Das hat zum einen den Vorteil, dass bei dieser Methode auch Callbacks behandelt werden, die zum Zeitpunkt der Entwicklung des Callback-Handlers noch gar nicht bekannt waren. Außerdem spart diese Methode am Anfang der Entwicklung einer HBCI-Anwendung Zeit. Zu einem späteren Zeitpunkt kann die Behandlung bestimmter Callback-Typen immer noch verfeinert werden.

Für den Anfang reicht es natürlich auch aus, eine der schon mitgelieferten HBCICallback-Klassen zu benutzen (`HBCICallbackConsole` oder `HBCICallbackSwing`).

Die Betrachtung der Callback-Methode `log()` soll an dieser Stelle ausgeklammert werden.

Die Methode `status()` des Callback-Interfaces dient dazu, der Anwendung mitzuteilen, in welchem Abarbeitungsschritt eines HBCI-Dialoges sich HBCI4Java gerade befindet. Dazu wird der Methode ein entsprechendes Tag (ein Integer-Wert) übergeben, der den aktuellen Abarbeitungsschritt identifiziert. Das Interface `HBCICallback` definiert `STATUS_*`-Konstanten für diese Integer-Werte. Normalerweise würde Code für die Auswertung dieser Tags in etwa so aussehen:

      public void status(HBCIPassport passport,int statusTag,Object[] o)
      {
          switch (statusTag) {
              case STATUS_DIALOG_INIT:
                  printStatusInfo("fuehre dialog-initialisierung aus");
                  break;
              case STATUS_DIALOG_INIT_DONE:
                  printStatusInfo("dialog-init durchgeführt, dialogid ist "+o[0].toString());
                  break;
              // usw. usf.
          }
      }

Das ist natürlich für eine Vielzahl von `STATUS_*`-Konstanten sehr aufwändig. Leider gibt es dafür im Moment auch keinen sinnvollen Workaround. Die pragmatischste Lösung wäre folgender Code:

        public void status(HBCIPassport passport,int statusTag,Object[] o)
        {
            try {
                Class cl=this.getClass();
                Field[] fields=cl.getFields();

                for (int i=0;i<fields.length;i++) {
                    Field field=fields[i];
                    String name=field.getName();

                    if (name.startsWith("STATUS_")) {
                        int value=field.getInt(this);
                        if (value==statusTag) {
                            printStatusInfo("status: "+name);
                            if (o!=null) {
                                for (int j=0;j<o.length;j++) {
                                    if (o[j] instanceof String)
                                        printStatusInfo("statusdata: "+o[j]);
                                    else
                                        printStatusInfo("class of status data: "+o[j].getClass().getName());
                                }
                            }
                            break;
                        }
                    }
                }
            } catch (Exception e) {
                throw new HBCI_Exception("error while getting name of constant for statusTag "+statusTag,e);
            }
        }

Damit würden zumindest der Name der entsprechenden Konstante sowie einige Informationen zu den zusätzlichen Daten extrahiert werden, und zwar ohne dass es nötig wäre, tatsächlich alle möglichen `STATUS_*`-Konstanten zu kennen.

Sinnvoller ist die Entwicklung eines generischen Handlers allerdings für die `callback()`-Methode. Diese Methode wird immer dann von HBCI4Java aufgerufen, wenn irgendeine Interaktion mit dem Anwender nötig ist. Dabei gibt es folgende "Unterfälle":

- es wird eine Aktion vom Anwender erwartet, wobei das Ende der Aktion von HBCI4Java selbst erkannt wird, also keine zusätzliche Eingabe oder Bestätigung vom Anwender benötigt (z.B. das Einlegen der Chipkarte in das Lesegerät)
- es wird eine Text-Eingabe vom Anwender erwartet
- es wird eine Entscheidung vom Anwender verlangt (ja/nein, weiter/abbrechen usw.)
- es muss eine Information angezeigt werden

Alle diese Fälle werden durch die Methode `callback()` abgedeckt und lassen sich anhand der jeweils übergebenen Parameter unterscheiden. Der Parameter `reason` gibt dabei die Ursache des Callbacks an. Das ist genau der Parameter, für den es sehr viele mögliche Belegungen gibt. In der Regel wird in einem Callback-Handler dieser Parameter als erstes ausgewertet -- je nach Wert ist eine entsprechende Reaktion vorgesehen. Es wird also i.d.R. ein ähnlicher `switch-case`-Block wie in einer `status()`-Methode implementiert werden. In einem generischen Callback-Handler soll dieser Parameter möglichst nicht ausgewertet werden müssen, weil hier ständig neue Werte hinzukommen können. Deshalb muss die richtige Reaktion auf einen Callback von den Werten der anderen Parameter abhängig gemacht werden.

Der Parameter `msg` enthält einen String, der die Ursache des Callbacks und die erwartete Reaktion/Aktion im Klartext beschreibt. Dieser Text kann also in jedem Fall anzeigt werden. Der Parameter `datatype` enthält den erwarteten Datentyp der Nutzereingabe. Ist der Wert dieses Parameters ungleich `TYPE_NONE`, so wird tatsächlich eine Nutzereingabe erwartet. In diesem Fall ist also ein entsprechendes Eingabefeld vorzusehen. HBCI4Java gibt dazu im Object `retData` bereits eine default-Eingabe vor. Diese ist in den meisten Fällen leer. Es ist auf jeden Fall zu empfehlen, diesen vorgeschlagenen default-Wert trotzdem anzuzeigen, da er in einigen Fällen tatsächlich benutzt wird (Das ist konkret beim Callback `HAVE_CRC_ERROR` der Fall, wo die evtl. zu korrigierenden Kontoverbindungsdaten im `retData`-Objekt übergeben werden). Die Nutzereingabe ist dann wieder in den StringBuffer `retData` einzustellen (im gleichen Datenformat wie der evtl. vorher darin enthaltene default-Wert). Nähere Informationen dazu ist in der Beschreibung der einzelnen `TYPE_*`-Konstanten zu finden.

Ist `datatype` allerdings gleich `TYPE_NONE`, so bedeutet das, dass keine Eingabe vom Anwender erwartet wird. In diesem Fall soll also entweder eine Information angezeigt werden (die Info selbst steht im Parameter `msg`), oder es wird eine Aktion vom Anwender erwartet, die er nicht explizit bestätigen muss. Die Unterscheidung zwischen beiden Fällen kann anhand des Parameters `retData` erfolgen: ist dieser Parameter gleich `null`, so handelt es sich um die Aufforderung zu einer Aktion, ist der Parameter ungleich `null`, so soll eine Information angezeigt werden, die der Anwender erst bestätigen muss.

Eine generische Callback-Methode kann also wiefolgt aussehen:

        public void callback(HBCIPassport passport,int reason,String msg,int datatype,StringBuffer retData)
        {
            try {
                output(msg);
                if (datatype!=TYPE_NONE) {
                    switch (datatype) {
                        case TYPE_TEXT:
                        case TYPE_SECRET: // dafuer muss nur die Eingabe durch Sternchen ("*") versteckt werden
                            // Eingabe in Rückgabeobjekt schreiben
                            output("current value: "+retData.toString());
                            retData.replace(0,retData.length(),readUserInput());
                            break;

                        case TYPE_BOOLEAN:
                            String input=readUserInput();
                            // Eingabe von "yes" für den einen Fall (TRUE),
                            // jeder andere Text führt zu dem anderen Fall (FALSE)
                            // siehe dazu Beschreibung von TYPE_BOOLEAN
                            if (input.equals("yes"))
                                retData.replace(0,retData.length(),"");
                            else
                                retData.replace(0,retData.length(),"error");
                            break;
                    }
                } else {
                    if (retData!=null) {
                        // auf bestätigung warten
                        waitForConfirmation();
                    }
                }
            } catch (Exception e) {
                throw new HBCI_Exception("error while handling callback",e);
            }
        }

Auf diese Art und Weise können alle existierenden und zukünftigen Callbacks mit einer einzigen generischen Methode behandelt werden. Der Nachteil ist natürlich, dass es an entsprechendem Komfort fehlt, den man bei bestimmten konkreten Callbacks anbieten könnte.