/* $Id: GVRKUms.java,v 1.3 2012/01/27 22:52:25 willuhn Exp $ This file is part of HBCI4Java Copyright (C) 2001-2008 Stefan Palme HBCI4Java is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. HBCI4Java is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package org.kapott.hbci.GV_Result; import java.io.Serializable; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.Iterator; import java.util.List; import org.kapott.hbci.exceptions.HBCI_Exception; import org.kapott.hbci.manager.HBCIUtils; import org.kapott.hbci.manager.HBCIUtilsInternal; import org.kapott.hbci.passport.HBCIPassport; import org.kapott.hbci.structures.Konto; import org.kapott.hbci.structures.Saldo; import org.kapott.hbci.structures.Value; import org.kapott.hbci.swift.Swift; /** <p>Ergebnisse der Abfrage von Kontoumsatzinformationen. Ein Objekt dieser Klasse entspricht einen Kontoauszug. Ein Kontoauszug ist in einzelne Buchungstage unterteilt. F�r jeden einzelnen Buchungstag wiederum gibt es eine Anzahl von Umsatzzeilen (das entspricht je einem Eintrag auf dem "normalen" Kontoauszug auf Papier). Jede einzelne Umsatzzeile wiederum enth�lt die einzelnen Informationen zu genau einer Transaktion. </p> <p>Es k�nnen auch alle Umsatzzeilen in einer einzigen Liste abgefragt werden (also nicht in Buchungstage unterteilt .</p>*/ public class GVRKUms extends HBCIJobResultImpl { /** Eine "Zeile" des Kontoauszuges (enth�lt Daten einer Transaktion) */ public static class UmsLine implements Serializable { /** Datum der Wertstellung */ public Date valuta; /** Buchungsdatum */ public Date bdate; /** Gebuchter Betrag */ public Value value; /** Handelt es sich um eine Storno-Buchung? */ public boolean isStorno; /** Der Saldo <em>nach</em> dem Buchen des Betrages <code>value</code> */ public Saldo saldo; /** Kundenreferenz */ public String customerref; /** Kreditinstituts-Referenz */ public String instref; /** Urspr�nglicher Betrag (bei ausl�ndischen Buchungen; optional) */ public Value orig_value; /** Betrag f�r Geb�hren des Geldverkehrs (optional) */ public Value charge_value; /** Art der Buchung (bankinterner Code). Nur wenn hier ein Wert ungleich * <code>999</code> drinsteht, enthalten die Attribute <code>text</code>, * <code>primanota</code>, <code>usage</code>, <code>other</code> und * <code>addkey</code> sinnvolle Werte. Andernfalls sind all diese * Informationen m�glicherweise im Feld <code>additional</code> enthalten, * allerdings in einem nicht definierten Format (siehe auch * <code>additional</code>). */ public String gvcode; /** <p>Zusatzinformationen im Rohformat. Wenn Zusatzinformationen zu dieser Transaktion in einem unbekannten Format vorliegen, dann enth�lt dieser String diese Daten (u.U. ist dieser String leer, aber nicht <code>null</code>). Das ist genau dann der Fall, wenn der Wert von <code>gvcode</code> gleich <code>999</code> ist.</p> <p>Wenn die Zusatzinformationen aber ausgewertet werden k�nnen (und <code>gvcode!=999</code>), so ist dieser String <code>null</code>, und die Felder <code>text</code>, <code>primanota</code>, <code>usage</code>, <code>other</code> und <code>addkey</code> enthalten die entsprechenden Werte (siehe auch <code>gvcode</code>)</p> */ public String additional; /** Beschreibung der Art der Buchung (optional). * Nur wenn <code>gvcode!=999</code>! (siehe auch <code>additional</code> * und <code>gvcode</code>)*/ public String text; /** Primanotakennzeichen (optional). * Nur wenn <code>gvcode!=999</code>! (siehe auch <code>additional</code> * und <code>gvcode</code>) */ public String primanota; /** Liste von Strings mit den Verwendungszweckzeilen. * Nur wenn <code>gvcode!=999</code>! (siehe auch <code>additional</code> * und <code>gvcode</code>)*/ public List<String> usage; /** Gegenkonto der Buchung (optional). * Nur wenn <code>gvcode!=999</code>! (siehe auch <code>additional</code> * und <code>gvcode</code>) */ public Konto other; /** Erweiterte Informationen zur Art der Buchung (bankintern, optional). * Nur wenn <code>gvcode!=999</code>! (siehe auch <code>additional</code> * und <code>gvcode</code>) */ public String addkey; /** Gibt an, ob ein Umsatz ein SEPA-Umsatz ist **/ public boolean isSepa; public UmsLine() { usage=new ArrayList<String>(); isSepa=false; } public void addUsage(String st) { if (st!=null) { usage.add(st); } } public String toString() { StringBuffer ret=new StringBuffer(); String linesep=System.getProperty("line.separator"); ret.append(HBCIUtils.date2StringLocal(valuta)).append(" ").append(HBCIUtils.date2StringLocal(bdate)).append(" "); ret.append(customerref).append(":").append(instref).append(" "); ret.append(value.toString()); ret.append(isStorno?" (Storno)":""); if (orig_value!=null) ret.append(" (orig ").append(orig_value.toString()).append(")"); if (charge_value!=null) ret.append(" (charge ").append(charge_value.toString()).append(")"); ret.append(linesep); ret.append(" saldo: ").append(saldo.toString()).append(linesep); ret.append(" code ").append(gvcode).append(linesep); if (additional==null) { ret.append(" text:").append(text).append(linesep); ret.append(" primanota:").append(primanota).append(linesep); for (Iterator<String> i=usage.iterator(); i.hasNext(); ) { ret.append(" usage:").append(i.next()).append(linesep); } if (other!=null) ret.append(" konto:").append(other.toString()).append(linesep); ret.append(" addkey:").append(addkey); } else ret.append(" ").append(additional); return ret.toString().trim(); } } /** Enth�lt alle Transaktionen eines einzelnen Buchungstages. Dazu geh�ren das Datum des jeweiligen Tages, der Anfangs- und Endsaldo sowie die Menge aller dazugeh�rigen Umsatzeilen */ public static class BTag implements Serializable { /** <p>Konto, auf das sich die Umsatzdaten beziehen (Kundenkonto). Einige Kreditinstitute geben fehlerhafte Kontoausz�ge zur�ck, was zur Folge haben kann, dass dieses Feld nicht richtig belegt ist. Tritt ein solcher Fall ein, so kann es vorkommen, dass von dem <code>Konto</code>-Objekt nur das Feld <code>number</code> gef�llt ist, und zwar mit den Informationen, die das Kreditinstitut zur Identifizierung dieses Kontos zur�ckgibt.</p> <p>Normalerweise bestehen diese Informationen aus BLZ und Kontonummer, die dann auch korrekt in das <code>Konto</code>-Objekt eingetragen werden. Liegen diese Informationen aber gar nicht oder in einem falschen bzw. unbekannten Format vor, so werden diese Daten komplett in das <code>number</code>-Feld geschrieben.</p> */ public Konto my; /** Nummer des Kontoauszuges (optional) */ public String counter; /** Saldo zu Beginn des Buchungstages */ public Saldo start; /** Art des Saldos. <code>M</code> = Anfangssaldo; <code>F</code> = Zwischensaldo */ public char starttype; /** Liste der einzelnen Buchungen dieses Tages (Instanzen von {@link GVRKUms.UmsLine}) */ public List<UmsLine> lines; /** Saldo am Ende des Buchungstages */ public Saldo end; /** Art des Endsaldos (siehe {@link #starttype}) */ public char endtype; public BTag() { lines=new ArrayList<UmsLine>(); } public void addLine(UmsLine line) { lines.add(line); } public String toString() { StringBuffer ret=new StringBuffer(); String linesep=System.getProperty("line.separator"); ret.append("Konto ").append(my.toString()).append(" - Auszugsnummer ").append(counter).append(linesep); ret.append(" ").append((starttype=='F'?"Anfangs":"Zwischen")).append("saldo: ").append(start.toString()).append(linesep); for (Iterator<UmsLine> i=lines.iterator(); i.hasNext(); ) { ret.append(" ").append(i.next().toString()).append(linesep); } ret.append(" ").append((endtype=='F'?"Schluss":"Zwischen")).append("saldo: ").append(end.toString()); return ret.toString().trim(); } } private StringBuffer bufferMT940; private StringBuffer bufferMT942; private List<BTag> tageMT940; private List<BTag> tageMT942; private boolean parsed; /** Dieses Feld enth�lt einen String, der den nicht-auswertbaren Teil der Kontoausz�ge * enth�lt. Es dient nur zu Debugging-Zwecken und sollte eigentlich immer <code>null</code> * bzw. einen leeren String enthalten. Wenn das nicht der Fall ist, dann konnten die * empfangenen Kontoausz�ge nicht richtig geparst werden, und dieser String enth�lt den * "Schwanz" der Kontoauszugsdaten, bei dem das Parsing-Problem aufgetreten ist. */ public StringBuffer restMT940; /** Wie restMT940, allerdings f�r die Daten der *vorgemerkten* Ums�tze. */ public StringBuffer restMT942; public GVRKUms() { bufferMT940=new StringBuffer(); bufferMT942=new StringBuffer(); tageMT940=new ArrayList<BTag>(); tageMT942=new ArrayList<BTag>(); restMT940=new StringBuffer(); restMT942=new StringBuffer(); parsed=false; } public void appendMT940Data(String data) { this.bufferMT940.append(data); } public void appendMT942Data(String data) { this.bufferMT942.append(data); } /** Gibt die Umsatzinformationen gruppiert nach Buchungstagen zur�ck. @return Liste mit Informationen zu einzelnen Buchungstagen ({@link GVRKUms.BTag}) */ public List<BTag> getDataPerDay() { verifyMT94xParsing("getDataPerDay()"); return tageMT940; } /** Gibt alle Transaktionsdatens�tze in einer "flachen" Struktur zur�ck. D.h. nicht in einzelne Buchungstage unterteilt, sondern in einer Liste analog zu einem "normalen" Kontoauszug. @return Liste mit Transaktionsdaten ({@link GVRKUms.UmsLine}) */ public List<UmsLine> getFlatData() { verifyMT94xParsing("getFlatData()"); List<UmsLine> result=new ArrayList<UmsLine>(); for (Iterator<BTag> i=tageMT940.iterator(); i.hasNext(); ) { BTag tag= i.next(); result.addAll(tag.lines); } return result; } /** Gibt eine Liste aller vorgemerkten Ums�tze zur�ck * @return Liste von {@link GVRKUms.UmsLine}-Objekten der vorgemerkten Ums�tze */ public List<UmsLine> getFlatDataUnbooked() { verifyMT94xParsing("getFlatDataUnbooked()"); List<UmsLine> result=new ArrayList<UmsLine>(); for (Iterator<BTag> i=tageMT942.iterator(); i.hasNext(); ) { BTag tag= i.next(); result.addAll(tag.lines); } return result; } public String toString() { verifyMT94xParsing("toString()"); StringBuffer ret=new StringBuffer(); String linesep=System.getProperty("line.separator"); // mt940 for (Iterator<UmsLine> i=getFlatData().iterator(); i.hasNext(); ) { ret.append(i.next().toString()).append(linesep); } ret.append("rest: ").append(restMT940).append(linesep).append(linesep); // mt942 ret.append("not yet booked:").append(linesep); for (Iterator<UmsLine> i=getFlatDataUnbooked().iterator(); i.hasNext(); ) { ret.append(i.next().toString()).append(linesep); } ret.append("rest: ").append(restMT942); return ret.toString().trim(); } private void verifyMT94xParsing(String where) { if (!parsed) { parseMT94x(bufferMT940, tageMT940, restMT940); parseMT94x(bufferMT942, tageMT942, restMT942); } if (restMT940!=null && restMT940.length()!=0) { HBCIUtils.log( where+ ": mt940 has not been parsed successfully " + "- probably returned data will be incomplete. "+ "check variable 'restMT940' (or set logging level to 4 (=DEBUG)) "+ "to see the data that could not be parsed.", HBCIUtils.LOG_WARN); HBCIUtils.log("restMT940: "+restMT940, HBCIUtils.LOG_DEBUG); } if (restMT942!=null && restMT942.length()!=0) { HBCIUtils.log( where+ ": mt942 has not been parsed successfully " + "- probably returned data will be incomplete. "+ "check variable 'restMT942' (or set logging level to 4 (=DEBUG)) "+ "to see the data that could not be parsed.", HBCIUtils.LOG_WARN); HBCIUtils.log("restMT942: "+restMT942, HBCIUtils.LOG_DEBUG); } } private void parseMT94x(StringBuffer buffer, List<BTag> tage, StringBuffer rest) { HBCIUtils.log("now parsing MT94x data", HBCIUtils.LOG_DEBUG); parsed=true; try { SimpleDateFormat dateFormat=new SimpleDateFormat("yyMMdd"); HBCIPassport passport=getPassport(); // split into "buchungstage" while (buffer.length()!=0) { String st_tag=Swift.getOneBlock(buffer); if (st_tag==null) { break; } GVRKUms.BTag btag=new GVRKUms.BTag(); // extract konto data String konto_info=Swift.getTagValue(st_tag,"25",0); int pos=konto_info.indexOf("/"); String blz; String number; String iban; String curr; if (pos!=-1) { blz=konto_info.substring(0,pos); number=konto_info.substring(pos+1); iban=""; curr=""; for (pos=number.length();pos>0;pos--) { char ch=number.charAt(pos-1); if (ch>='0' && ch<='9') break; } if (pos<number.length()) { curr=number.substring(pos); number=number.substring(0,pos); } } else { blz=""; number=""; iban=konto_info; curr=""; } btag.my=new Konto(); btag.my.blz=blz; btag.my.number=number; btag.my.iban=iban; btag.my.curr=curr; if (passport!=null) { passport.fillAccountInfo(btag.my); } // extract "auszugsnummer" btag.counter=Swift.getTagValue(st_tag,"28C",0); // extract "anfangssaldo" String st_start=Swift.getTagValue(st_tag,"60F",0); char starttype='F'; if (st_start==null) { st_start=Swift.getTagValue(st_tag,"60M",0); starttype='M'; } if (st_start!=null) { // Tag 60 (Anfangssaldo) gibt es in MT942 nicht, // darum wird btag.start nur in MT940 gef�llt btag.start=new Saldo(); btag.starttype=starttype; String cd=st_start.substring(0,1); try { btag.start.timestamp=dateFormat.parse(st_start.substring(1,7)); } catch (Exception e) { btag.start.timestamp=null; } // hier aus dem CD-Indikator und dem absoluten Saldo-Betrag // einen String f�r den Saldo-Betrag zusamennbauen btag.start.value=new Value( (cd.equals("D")?"-":"")+st_start.substring(10).replace(',','.'), st_start.substring(7,10)); } // looping to get all "umsaetze" // TODO: beim MT942 (btag.start==null) m�sste als Initialwert // fuer den Saldo hier eigentlich der Abschluss-Saldo aus den // gebuchten Ums�tzen verwendet werden (den habe ich an dieser // Stelle aber nicht so ohne weiteres) long saldo = (btag.start!=null)?btag.start.value.getLongValue():0; int ums_counter=0; while (true) { String st_ums=Swift.getTagValue(st_tag,"61",ums_counter); if (st_ums==null) break; GVRKUms.UmsLine line=new GVRKUms.UmsLine(); // extract valuta line.valuta=dateFormat.parse(st_ums.substring(0,6)); // extract bdate int next=0; if (st_ums.charAt(6)>'9') { // [2012-01-27 - Patch von Frank/Pecunia] // beim :61er Tag ist das Buchungsdatum optional. Wenn es nicht gesetzt ist, muss das Buchungsdatum des // Umsatzes z.B. aus :60F kommen if (btag.start != null && btag.start.timestamp != null) line.bdate = btag.start.timestamp; else line.bdate=line.valuta; next=6; } else { line.bdate=dateFormat.parse(st_ums.substring(0,2)+ st_ums.substring(6,10)); // wenn bdate und valuta um mehr als einen monat voneinander // abweichen, dann ist das jahr des bdate falsch (1.1.2005 vs. 31.12.2004) // korrektur des bdate-jahres in die richtige richtung notwendig // FE: ein Monat reicht nicht, es sollte schon ein halbes Jahr sein - es gab verschiedene Probleme mit Umsaetzen im falschen Jahr!! // http://www.onlinebanking-forum.de/phpBB2/viewtopic.php?p=75348 if (Math.abs(line.bdate.getTime()-line.valuta.getTime())>180L*24*3600*1000) { int diff; if (line.bdate.before(line.valuta)) { diff=+1; } else { diff=-1; } Calendar cal=Calendar.getInstance(); cal.setTime(line.bdate); cal.set(Calendar.YEAR,cal.get(Calendar.YEAR)+diff); line.bdate=cal.getTime(); } next=10; } // extract credit/debit String cd; if (st_ums.charAt(next)=='C' || st_ums.charAt(next)=='D') { line.isStorno=false; cd=st_ums.substring(next,next+1); next++; } else { line.isStorno=true; cd=st_ums.substring(next+1,next+2); next+=2; } // skip part of currency char currpart=st_ums.charAt(next); if (currpart>'9') next++; line.value=new Value(); // TODO: bei einem MT942 wird die waehrung hier automatisch auf EUR // gesetzt, weil die auto-erkennung (anhand des anfangssaldos) hier nicht // funktioniert, weil es im MT942 keinen anfangssaldo gibt line.value.setCurr((btag.start!=null)?btag.start.value.getCurr():"EUR"); // extract value and skip code int npos=st_ums.indexOf("N",next); // welcher Code (C/D) zeigt einen negativen Buchungsbetrag // an? Bei einer "normalen" Buchung ist das D(ebit). Bei // einer Storno-Buchung ist der Betrag allerdings negativ, // wenn eine ehemalige Gutschrift (Credit) storniert wird, // in dem Fall w�re als "C" der Indikator f�r den negativen // Buchungsbetrag String negValueIndikator=line.isStorno?"C":"D"; line.value.setValue( HBCIUtilsInternal.string2Long( (cd.equals(negValueIndikator)?"-":"") + st_ums.substring(next,npos).replace(',','.'), 100)); next=npos+4; // update saldo saldo+=line.value.getLongValue(); line.saldo=new Saldo(); line.saldo.timestamp=line.bdate; // TODO: bei einem MT942 wird die waehrung hier automatisch auf EUR // gesetzt, weil die auto-erkennung (anhand des anfangssaldos) hier nicht // funktioniert, weil es im MT942 keinen anfangssaldo gibt line.saldo.value=new Value(saldo, (btag.start!=null)?btag.start.value.getCurr():"EUR"); // extract customerref npos=st_ums.indexOf("//",next); if (npos==-1) npos=st_ums.indexOf("\r\n",next); if (npos==-1) npos=st_ums.length(); line.customerref=st_ums.substring(next,npos); next=npos; // check for instref if (next<st_ums.length() && st_ums.substring(next,next+2).equals("//")) { // extract instref next+=2; npos=st_ums.indexOf("\r\n",next); if (npos==-1) npos=st_ums.length(); line.instref=st_ums.substring(next,npos); next=npos+2; } if (line.instref==null) line.instref=""; // check for additional information if (next<st_ums.length() && st_ums.charAt(next)=='\r') { next+=2; // extract orig Value pos=st_ums.indexOf("/OCMT/",next); if (pos!=-1) { int slashpos=st_ums.indexOf("/",pos+9); if (slashpos==-1) slashpos=st_ums.length(); try { line.orig_value=new Value( st_ums.substring(pos+9,slashpos).replace(',','.'), st_ums.substring(pos+6,pos+9)); } catch (NumberFormatException nfe) { // Der Betrag darf fehlen. Tolerieren wir } } // extract charge Value pos=st_ums.indexOf("/CHGS/",next); if (pos!=-1) { int slashpos=st_ums.indexOf("/",pos+9); if (slashpos==-1) slashpos=st_ums.length(); try { line.charge_value=new Value( st_ums.substring(pos+9,slashpos).replace(',','.'), st_ums.substring(pos+6,pos+9)); } catch (NumberFormatException nfe) { // Der Betrag darf fehlen. Tolerieren wir } } } String st_multi=Swift.getTagValue(st_tag,"86",ums_counter); if (st_multi!=null) { line.gvcode=st_multi.substring(0,3); st_multi=Swift.packMulti(st_multi.substring(3)); if (!line.gvcode.equals("999")) { line.isSepa = line.gvcode.startsWith("1"); line.text=Swift.getMultiTagValue(st_multi,"00"); line.primanota=Swift.getMultiTagValue(st_multi,"10"); for (int i=0;i<10;i++) { line.addUsage(Swift.getMultiTagValue(st_multi,Integer.toString(20+i))); } Konto acc=new Konto(); acc.blz=Swift.getMultiTagValue(st_multi,"30"); acc.number=Swift.getMultiTagValue(st_multi,"31"); // fuer den Fall, dass in der BLZ sowas hier drin steht: "GENODEF1S06 SVWZ+ ja" // Siehe http://www.onlinebanking-forum.de/phpBB2/viewtopic.php?t=16182 if (acc.blz != null) { int space = acc.blz.indexOf(" "); if (space != -1) { HBCIUtils.log("blz/bic \"" + acc.blz + "\" contains invalid chars, trimming after first space", HBCIUtils.LOG_DEBUG); acc.blz = acc.blz.substring(0,space); } } if (line.isSepa) { acc.bic = acc.blz; acc.iban = acc.number; } acc.name=Swift.getMultiTagValue(st_multi,"32"); acc.name2=Swift.getMultiTagValue(st_multi,"33"); if (acc.blz!=null || acc.number!=null || acc.name!=null || acc.name2!=null) { if (acc.blz==null) acc.blz=""; if (acc.number==null) acc.number=""; if (acc.name==null) acc.name=""; line.other=acc; } line.addkey=Swift.getMultiTagValue(st_multi,"34"); for (int i=0;i<4;i++) { line.addUsage(Swift.getMultiTagValue(st_multi,Integer.toString(60+i))); } } else { line.additional=st_multi; } } btag.addLine(line); ums_counter++; } // extract "schlusssaldo" String st_end=Swift.getTagValue(st_tag,"62F",0); char endtype='F'; btag.endtype='F'; if (st_end==null) { st_end=Swift.getTagValue(st_tag,"62M",0); endtype='M'; } if (st_end!=null) { // Tag 62 (Schlusssaldo) gibt es in MT942 nicht, // darum wird btag.end nur in MT940 gef�llt btag.end=new Saldo(); btag.endtype=endtype; String cd=st_end.substring(0,1); try { btag.end.timestamp=dateFormat.parse(st_end.substring(1,7)); } catch (Exception e) { btag.end.timestamp=null; } // set default values for optional non-given bdates if (btag.start != null && btag.start.timestamp==null) { btag.start.timestamp=btag.end.timestamp; } for (Iterator<UmsLine> j=btag.lines.iterator(); j.hasNext(); ) { UmsLine line= j.next(); if (line.bdate==null) { line.bdate=btag.end.timestamp; } } btag.end.value=new Value( (cd.equals("D")?"-":"")+st_end.substring(10).replaceAll("\\s","").replace(',','.'), st_end.substring(7,10)); } // Now check if the end balance (Schlusssaldo) equals balance of last statement. If not, the bank sent a wrong start balance // and we have to re-calculate the balances for each statement int numLines = btag.lines.size(); if(numLines > 0 && btag.end != null) { UmsLine lastLine = btag.lines.get(numLines-1); saldo = btag.end.value.getLongValue(); if(lastLine.saldo.value.getLongValue() != saldo) { for(int i=numLines-1; i>=0; i--) { lastLine = btag.lines.get(i); lastLine.saldo.value = new Value(saldo, btag.end.value.getCurr()); saldo -= lastLine.value.getLongValue(); } } } tage.add(btag); buffer.delete(0,st_tag.length()); } // remove this debugging output // HBCIUtils.log("Parsing of MT940 ok until now; unparsed data: "+buffer,HBCIUtils.LOG_DEBUG2); } catch (Exception e) { HBCIUtils.log("There is unparsed MT94x data - an exception occured while parsing",HBCIUtils.LOG_ERR); HBCIUtils.log("current MT94x buffer: "+buffer,HBCIUtils.LOG_DEBUG2); throw new HBCI_Exception(e); } finally { rest.setLength(0); rest.append(buffer.toString()); } } }