/* $Id: HBCIBatch.java,v 1.1 2011/05/04 22:37:45 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.tools; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; import java.util.Hashtable; import java.util.Properties; import java.util.StringTokenizer; import org.kapott.hbci.GV.HBCIJob; import org.kapott.hbci.callback.HBCICallbackConsole; import org.kapott.hbci.exceptions.HBCI_Exception; import org.kapott.hbci.manager.HBCIHandler; import org.kapott.hbci.manager.HBCIUtils; import org.kapott.hbci.manager.HBCIUtilsInternal; import org.kapott.hbci.passport.AbstractHBCIPassport; import org.kapott.hbci.passport.HBCIPassport; /** Tool zum Ausf�hren von HBCI-Jobs, die in einer Batch-Datei definiert werden * k�nnen. * <pre> * args[0] - configfile f�r HBCIUtils.init() (Property-File mit Kernel-Parametern * [siehe API-Doc zu org.kapott.hbci.manager.HBCIUtils]) * zus�tzliche parameter: * client.passport.default= * default.hbciversion= * * args[1] - Dateiname der Antwortdatei f�r Callbacks * country= * blz= * host= * port= * filter= * userid= * customerid= * sizentry= * passphrase= * softpin= * pin= * tans= * * args[2] - Dateiname der Batch-Datei (jobnamen und parameter siehe * API-Doc zu Paket org.kapott.hbci.GV) * # kommentar * * hljobname:jobid:(props|toString)[:customerid] * hljobparam=paramvalue * hljobparam=<filename * ... * * _lljobname:jobid[:customerid] * _lljobparam=paramvalue * _lljobparam=<filename * ... * * --[:customerid] * * args[3] - Dateiname der Ausgabedatei (mehr dazu siehe unten) * jobid:XXXX * job status: * YYYYYYYYYYY * ZZZZZZZZZZZ * ... * job result: * resultparam=value * resultparam=value * * ... * [args[4]] - Dateiname der Log-Datei * </pre> * * <p>Alle Jobs, bei deren Ausf�hrung ein Fehler auftritt, werden nicht in die * "normale" Ausgabedatei aufgenommen. Statt dessen wird eine zweite Aus- * gabedatei erzeugt, die den gleichen Namen wie die "normale" Ausgabedatei * plus ein Suffix ".err" hat. In dieser Fehlerdatei wird f�r jeden fehler- * haften Job folgende Struktur geschrieben (String in "<>" wird durch die * jeweiligen werte ersetzt):</p> * <pre> * jobid:JOBID * global status: * allg. fehlermeldung zur hbci-nachricht, in der der job ausgef�hrt werden sollte * job status: * fehlermeldung zu dem nachrichten-segment, in welchem der job definiert war * * ... * </pre> * <p>das ist zwar nicht besonders sch�n, reicht aber vielleicht erst mal (?) * Alternativ dazu k�nnte ich anbieten, dass eine vollst�ndige Fehlernachricht * �ber den *kompletten* Batch-Vorgang in eine Fehlerdatei geschrieben wird, * sobald *irgendein* Job nicht sauber ausgef�hrt wurde (das h�tte den Vorteil, * dass auch Fehler, die nicht direkt mit einem bestimmten Job in Verbindung * stehen [z.B. Fehler bei der Dialog-Initialisierung] ordentlich geloggt * werden).</p> */ public class HBCIBatch { // speziell callback-klasse, um die ausgaben zu reduzieren und um die // nutzer-interaktion zu unterbinden, indem alle abgefragten daten auto- // tisch �bergeben werden (aus args[1]) private static class MyCallback extends HBCICallbackConsole { private Properties answers; // alle spezifizierten antwortdaten public MyCallback(String[] args) throws FileNotFoundException,IOException { // einlesen der answers-datei answers=new Properties(); FileInputStream answerFile=new FileInputStream(args[1]); answers.load(answerFile); answerFile.close(); // wenn ein logfile angegeben wurde, dann dieses als ausgabemedium // f�r stdout und stderr verwenden if (args.length>=5) { PrintStream outStream=new PrintStream(new FileOutputStream(args[4])); System.setOut(outStream); System.setErr(outStream); this.setOutStream(outStream); } } // modifizierte callback-methode, die daten-anfragen "automatisch" // beantwortet public synchronized void callback(HBCIPassport passport,int reason,String msg,int datatype,StringBuffer retData) { switch (reason) { case NEED_CHIPCARD: System.out.println(HBCIUtilsInternal.getLocMsg("CALLB_NEED_CHIPCARD")); break; case NEED_HARDPIN: System.out.println(HBCIUtilsInternal.getLocMsg("CALLB_NEED_HARDPIN")); break; case NEED_SOFTPIN: retData.replace(0,retData.length(),answers.getProperty("softpin")); break; case NEED_PASSPHRASE_LOAD: case NEED_PASSPHRASE_SAVE: retData.replace(0,retData.length(),answers.getProperty("passphrase")); break; case NEED_PT_SECMECH: retData.replace(0,retData.length(),answers.getProperty("secmech")); break; case NEED_PT_PIN: retData.replace(0,retData.length(),answers.getProperty("pin")); break; case NEED_PT_TAN: case NEED_PT_PHOTOTAN: // TODO tan-liste aktivieren retData.replace(0,retData.length(),answers.getProperty("tan")); break; case NEED_COUNTRY: retData.replace(0,retData.length(),answers.getProperty("country")); break; case NEED_BLZ: retData.replace(0,retData.length(),answers.getProperty("blz")); break; case NEED_HOST: retData.replace(0,retData.length(),answers.getProperty("host")); break; case NEED_PORT: retData.replace(0,retData.length(),answers.getProperty("port")); break; case NEED_FILTER: retData.replace(0,retData.length(),answers.getProperty("filter")); break; case NEED_USERID: retData.replace(0,retData.length(),answers.getProperty("userid")); break; case NEED_CUSTOMERID: retData.replace(0,retData.length(),answers.getProperty("customerid")); break; case NEED_SIZENTRY_SELECT: retData.replace(0,retData.length(),answers.getProperty("sizentry")); break; case NEED_NEW_INST_KEYS_ACK: retData.replace(0,retData.length(),""); break; case HAVE_NEW_MY_KEYS: System.out.println("please restart batch process"); break; case HAVE_INST_MSG: HBCIUtils.log(msg,HBCIUtils.LOG_INFO); break; case NEED_CONNECTION: case CLOSE_CONNECTION: break; } } // ausgabe der status-meldungen komplett unterbinden public synchronized void status(HBCIPassport passport,int statusTag, Object[] objs) { } } private final static int STATE_NEED_JOBNAME=1; // state-flags f�r private final static int STATE_NEED_JOBPARAMS=2; // batch-file-parser public static void main(String[] args) throws Exception { // initialisieren von hbci4java Properties props=new Properties(); InputStream istream=new FileInputStream(args[0]); props.load(istream); istream.close(); HBCIUtils.init(props, new MyCallback(args)); // erzeugen des passport-objektes HBCIPassport passport=AbstractHBCIPassport.getInstance(); try { // initialisieren des hbci-handlers f�r das passport String version=passport.getHBCIVersion(); HBCIHandler handler=new HBCIHandler(version.length()!=0?version:HBCIUtils.getParam("default.hbciversion"),passport); try { // batch-datei �ffnen BufferedReader reader=new BufferedReader(new FileReader(args[2])); String line; try { int state=STATE_NEED_JOBNAME; boolean lljob=false; // low- oder high-level-job? HBCIJob job=null; // job-objekt String jobid=null; // job-bezeichner String customerId=null; // customer-id f�r job Hashtable<String, Object> jobs=new Hashtable<String, Object>(); // liste aller jobs // batch-datei zeilenweise einlesen und auswerten while ((line=reader.readLine())!=null) { line=line.trim(); // kommentare ignorieren if (line.startsWith("#")) { continue; } if (state==STATE_NEED_JOBNAME && line.length()!=0) { // es wird der beginn einer job-definition erwartet StringTokenizer tok=new StringTokenizer(line,":"); // jobnamen extrahieren String jobname=tok.nextToken().trim(); if (jobname.equals("--")) { // wenn jobname="--", dann neue hbci-message erzeugen customerId=(tok.hasMoreTokens()?tok.nextToken().trim():null); handler.newMsg(customerId); } else { // ansonsten handelt es sich um einen "richtigen" job String resultMode; if (jobname.startsWith("_")) { // wenn jobname mit "_" beginnt, handelt es // sich um einen low-level-jobnamen job=handler.newLowlevelJob(jobname.substring(1)); lljob=true; // zu einem low-level-job m�ssen zus�tzlich noch // eine ID (zum sp�teren wiederfinden des jobs) // und optional eine kunden-id festgelegt werden jobid=tok.nextToken().trim(); resultMode="toString"; customerId=(tok.hasMoreTokens()?tok.nextToken().trim():null); } else { // wenn jobname nicht mit "_" beginnt, ist es // ein high-level-job job=handler.newJob(jobname); lljob=false; // zu einem high-level-job m�ssen zus�tzlich noch // eine ID (zum sp�teren wiederfinden des jobs), // ein modus f�r die ausgabe der ergebisdaten // und optional eine kunden-id festgelegt werden jobid=tok.nextToken().trim(); resultMode=tok.nextToken().trim(); customerId=(tok.hasMoreTokens()?tok.nextToken().trim():null); } // job in menge der jobs speichern jobs.put(jobid,job); // ... und ausgabemodus f�r diesen job merken jobs.put(jobid+"_resultMode",resultMode); state=STATE_NEED_JOBPARAMS; } } else if (state==STATE_NEED_JOBPARAMS) { // bis zur n�chsten leerzeile oder dem dateienende // werden jetzt alle zeilen als job-parameter // interpretiert if (line.length()!=0) { StringTokenizer tok=new StringTokenizer(line,"="); // parameternamen und -wert holen String paramName=tok.nextToken().trim(); if (!tok.hasMoreTokens()) { continue; } String paramValue=tok.nextToken().trim(); // f�r low-level-jobs m�ssen die parameter mit // einem "_" beginnen, bei high-level-jobs // d�rfen sie *nicht* mit einem "_" beginnen if (paramName.startsWith("_")!=lljob) { if (lljob) { throw new HBCI_Exception("*** "+jobid+" is a lowlevel job, so parameter names have to start with '_'"); } throw new HBCI_Exception("*** "+jobid+" is a highlevel job, so parameter names must not start with '_'"); } // wenn es sich um einen low-level-job, den // f�hrenden "_" beim parameter-namen entfernen if (lljob) { paramName=paramName.substring(1); } // wenn der parameter-wert mit einem "<" beginnt, // so soll der wert des parameter aus der datei // gelesen werden, die nach dem "<" spezifiziert // ist if (paramValue.startsWith("<")) { // �ffnen der datei String filename=paramValue.substring(1); FileInputStream fin=new FileInputStream(filename); // puffer f�r einlesen der datei byte[] buffer=new byte[2048]; int len; StringBuffer content=new StringBuffer(); // datei in stringbuffer einlesen while ((len=fin.read(buffer))>0) { content.append(new String(buffer,0,len,"ISO-8859-1")); } // datei schlie�en fin.close(); // parameterwert ist inhalt des stringbuffers paramValue=content.toString(); } // parameter f�r aktuellen job setzen job.setParam(paramName,paramValue); } else { // leerzeile gefunden - damit ist die parameter- // spez. f�r den aktuellen job beendet // aktuellen job zur job-queue hinzuf�gen job.addToQueue(customerId); state=STATE_NEED_JOBNAME; } } } // wenn noch ein job "in bearbeitung" ist, der noch nicht // zur job-queue hinzugef�gt wurde, dann das jetzt nachholen if (state==STATE_NEED_JOBPARAMS) { job.addToQueue(customerId); } // alle batch-jobs ausf�hren handler.execute(); // ergebnis-writer f�r ok-jobs und f�r fehlerhafte jobs // erzeugen PrintWriter writer=new PrintWriter(new FileWriter(args[3])); PrintWriter errWriter=new PrintWriter(new FileWriter(args[3]+".err")); try { // alle bekannten job-bezeichner (IDs) durchlaufen for (Enumeration<String> jobIds=jobs.keys();jobIds.hasMoreElements();) { jobid=jobIds.nextElement(); if (jobid.endsWith("_resultMode")) { continue; } // den dazugeh�rigen job holen job=(HBCIJob)jobs.get(jobid); if (job.getJobResult().isOK()) { // wenn der job erfolgreich gelaufen ist // ausgabe von jobid writer.println("jobid:"+jobid); // ausgabe der hbci-status-meldungen zu diesem job writer.println("job status:"); writer.println(job.getJobResult().getJobStatus()); // ausgabe der job-ergebnisse writer.println("job result:"); String resultMode=(String)jobs.get(jobid+"_resultMode"); if (resultMode.equals("props")) { // ausgabemodus="props": alle ergebnisdaten // als lowlevel-properties ausgeben Properties result=job.getJobResult().getResultData(); if (result!=null) { // array mit result-properties holen und // sortieren String[] keys=(String[])new ArrayList(result.keySet()).toArray(new String[0]); Arrays.sort(keys); // ausgabe aller result-properties for (int i=0;i<keys.length;i++) { String name=keys[i]; String value=result.getProperty(name); writer.println(name+"="+value); } } } else { // ausgabemodus="toString": job-spezifische // toString()-methode f�r formatierung der // ergebnisdaten aufrufen writer.println(job.getJobResult()); } // leerzeile einf�gen writer.println(); } else { // wenn ein job fehler erzeugt hatte, die fehlermeldungen // an die err-datei anh�ngen errWriter.println("jobid:"+jobid); errWriter.println("global status:"); errWriter.println(job.getJobResult().getGlobStatus().getErrorString()); errWriter.println("job status:"); errWriter.println(job.getJobResult().getJobStatus().getErrorString()); errWriter.println(); } } } finally { writer.close(); errWriter.close(); } } finally { reader.close(); } } finally { handler.close(); passport=null; } } finally { if (passport!=null) { passport.close(); } } } }